CCodec: move Buffers out into a separate file

Seperate Buffers out before we add complex logic to them. Also add
missing documentation.

Bug: 130223947
Test: builds
Change-Id: I8f992cb36499401ee2c6d729aa52f3888a7bcea5
diff --git a/media/codec2/sfplugin/CCodecBuffers.cpp b/media/codec2/sfplugin/CCodecBuffers.cpp
new file mode 100644
index 0000000..fb0efce
--- /dev/null
+++ b/media/codec2/sfplugin/CCodecBuffers.cpp
@@ -0,0 +1,963 @@
+/*
+ * 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 <C2PlatformSupport.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MediaCodecConstants.h>
+
+#include "CCodecBuffers.h"
+
+namespace android {
+
+namespace {
+
+sp<GraphicBlockBuffer> AllocateGraphicBuffer(
+        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;
+    }
+
+    std::shared_ptr<C2GraphicBlock> block;
+    c2_status_t err = pool->fetchGraphicBlock(
+            width, height, pixelFormat, usage, &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) {
+        return;
+    }
+    sp<ABuffer> imageData;
+    if (!mFormat->findBuffer("image-data", &imageData)
+            || imageDataCandidate->size() != imageData->size()
+            || memcmp(imageDataCandidate->data(), imageData->data(), imageData->size()) != 0) {
+        ALOGD("[%s] updating image-data", mName);
+        sp<AMessage> newFormat = dupFormat();
+        newFormat->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;
+            newFormat->setInt32(KEY_STRIDE, stride);
+            ALOGD("[%s] updating stride = %d", mName, stride);
+            if (img->mNumPlanes > 1 && stride > 0) {
+                int32_t vstride = (img->mPlane[1].mOffset - img->mPlane[0].mOffset) / stride;
+                newFormat->setInt32(KEY_SLICE_HEIGHT, vstride);
+                ALOGD("[%s] updating vstride = %d", mName, vstride);
+            }
+        }
+        setFormat(newFormat);
+        buffer->setFormat(newFormat);
+    }
+}
+
+// OutputBuffers
+
+void OutputBuffers::initSkipCutBuffer(
+        int32_t delay, int32_t padding, int32_t sampleRate, int32_t channelCount) {
+    CHECK(mSkipCutBuffer == nullptr);
+    mDelay = delay;
+    mPadding = padding;
+    mSampleRate = sampleRate;
+    setSkipCutBuffer(delay, padding, channelCount);
+}
+
+void OutputBuffers::updateSkipCutBuffer(int32_t sampleRate, int32_t channelCount) {
+    if (mSkipCutBuffer == nullptr) {
+        return;
+    }
+    int32_t delay = mDelay;
+    int32_t padding = mPadding;
+    if (sampleRate != mSampleRate) {
+        delay = ((int64_t)delay * sampleRate) / mSampleRate;
+        padding = ((int64_t)padding * sampleRate) / mSampleRate;
+    }
+    setSkipCutBuffer(delay, padding, channelCount);
+}
+
+void OutputBuffers::submit(const sp<MediaCodecBuffer> &buffer) {
+    if (mSkipCutBuffer != nullptr) {
+        mSkipCutBuffer->submit(buffer);
+    }
+}
+
+void OutputBuffers::transferSkipCutBuffer(const sp<SkipCutBuffer> &scb) {
+    mSkipCutBuffer = scb;
+}
+
+void OutputBuffers::setSkipCutBuffer(int32_t skip, int32_t cut, int32_t channelCount) {
+    if (mSkipCutBuffer != nullptr) {
+        size_t prevSize = mSkipCutBuffer->size();
+        if (prevSize != 0u) {
+            ALOGD("[%s] Replacing SkipCutBuffer holding %zu bytes", mName, prevSize);
+        }
+    }
+    mSkipCutBuffer = new SkipCutBuffer(skip, cut, channelCount);
+}
+
+// LocalBufferPool
+
+std::shared_ptr<LocalBufferPool> LocalBufferPool::Create(size_t poolCapacity) {
+    return std::shared_ptr<LocalBufferPool>(new LocalBufferPool(poolCapacity));
+}
+
+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();
+        }
+        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));
+}
+
+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;
+}
+
+// FlexBuffersImpl
+
+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();
+        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::numClientBuffers() const {
+    return std::count_if(
+            mBuffers.begin(), mBuffers.end(),
+            [](const Entry &entry) {
+                return (entry.clientBuffer != nullptr);
+            });
+}
+
+// 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();
+        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 });
+    }
+}
+
+size_t BuffersArrayImpl::numClientBuffers() const {
+    return std::count_if(
+            mBuffers.begin(), mBuffers.end(),
+            [](const Entry &entry) {
+                return entry.ownedByClient;
+            });
+}
+
+// InputBuffersArray
+
+void InputBuffersArray::initialize(
+        const FlexBuffersImpl &impl,
+        size_t minSize,
+        std::function<sp<Codec2Buffer>()> 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::numClientBuffers() const {
+    return mImpl.numClientBuffers();
+}
+
+// LinearInputBuffers
+
+bool LinearInputBuffers::requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) {
+    int32_t capacity = kLinearBufferSize;
+    (void)mFormat->findInt32(KEY_MAX_INPUT_SIZE, &capacity);
+    if ((size_t)capacity > kMaxLinearBufferSize) {
+        ALOGD("client requested %d, capped to %zu", capacity, kMaxLinearBufferSize);
+        capacity = kMaxLinearBufferSize;
+    }
+    // TODO: proper max input size
+    // TODO: read usage from intf
+    sp<Codec2Buffer> newBuffer = alloc((size_t)capacity);
+    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) {
+    int32_t capacity = kLinearBufferSize;
+    (void)mFormat->findInt32(KEY_MAX_INPUT_SIZE, &capacity);
+    if ((size_t)capacity > kMaxLinearBufferSize) {
+        ALOGD("client requested %d, capped to %zu", capacity, kMaxLinearBufferSize);
+        capacity = kMaxLinearBufferSize;
+    }
+    // TODO: proper max input size
+    // TODO: read usage from intf
+    std::unique_ptr<InputBuffersArray> array(
+            new InputBuffersArray(mComponentName.c_str(), "1D-Input[N]"));
+    array->setPool(mPool);
+    array->setFormat(mFormat);
+    array->initialize(
+            mImpl,
+            size,
+            [this, capacity] () -> sp<Codec2Buffer> { return alloc(capacity); });
+    return std::move(array);
+}
+
+size_t LinearInputBuffers::numClientBuffers() const {
+    return mImpl.numClientBuffers();
+}
+
+sp<Codec2Buffer> LinearInputBuffers::alloc(size_t size) {
+    C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+    std::shared_ptr<C2LinearBlock> block;
+
+    c2_status_t err = mPool->fetchLinearBlock(size, usage, &block);
+    if (err != C2_OK) {
+        return nullptr;
+    }
+
+    return LinearBlockBuffer::Allocate(mFormat, block);
+}
+
+// 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),
+      mHeapSeqNum(heapSeqNum) {
+    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});
+    }
+}
+
+sp<Codec2Buffer> EncryptedLinearInputBuffers::alloc(size_t size) {
+    sp<IMemory> memory;
+    size_t slot = 0;
+    for (; slot < mMemoryVector.size(); ++slot) {
+        if (mMemoryVector[slot].block.expired()) {
+            memory = mMemoryVector[slot].memory;
+            break;
+        }
+    }
+    if (memory == nullptr) {
+        return nullptr;
+    }
+
+    std::shared_ptr<C2LinearBlock> block;
+    c2_status_t err = mPool->fetchLinearBlock(size, mUsage, &block);
+    if (err != C2_OK || block == nullptr) {
+        return nullptr;
+    }
+
+    mMemoryVector[slot].block = block;
+    return new EncryptedLinearBlockBuffer(mFormat, block, memory, mHeapSeqNum);
+}
+
+// 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) {
+    std::shared_ptr<C2Allocator> alloc;
+    c2_status_t err = mStore->fetchAllocator(mPool->getAllocatorId(), &alloc);
+    if (err != C2_OK) {
+        return false;
+    }
+    sp<GraphicMetadataBuffer> newBuffer = new GraphicMetadataBuffer(mFormat, alloc);
+    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::numClientBuffers() const {
+    return mImpl.numClientBuffers();
+}
+
+// GraphicInputBuffers
+
+GraphicInputBuffers::GraphicInputBuffers(
+        size_t numInputSlots, const char *componentName, const char *name)
+    : InputBuffers(componentName, name),
+      mImpl(mName),
+      mLocalBufferPool(LocalBufferPool::Create(
+              kMaxLinearBufferSize * numInputSlots)) { }
+
+bool GraphicInputBuffers::requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) {
+    // TODO: proper max input size
+    // TODO: read usage from intf
+    C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+    sp<GraphicBlockBuffer> newBuffer = AllocateGraphicBuffer(
+            mPool, mFormat, HAL_PIXEL_FORMAT_YV12, usage, mLocalBufferPool);
+    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.
+}
+
+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);
+    array->initialize(
+            mImpl,
+            size,
+            [pool = mPool, format = mFormat, lbp = mLocalBufferPool]() -> sp<Codec2Buffer> {
+                C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+                return AllocateGraphicBuffer(
+                        pool, format, HAL_PIXEL_FORMAT_YV12, usage, lbp);
+            });
+    return std::move(array);
+}
+
+size_t GraphicInputBuffers::numClientBuffers() const {
+    return mImpl.numClientBuffers();
+}
+
+// OutputBuffersArray
+
+void OutputBuffersArray::initialize(
+        const FlexBuffersImpl &impl,
+        size_t minSize,
+        std::function<sp<Codec2Buffer>()> 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 (!c2Buffer->copy(buffer)) {
+        ALOGD("[%s] copy buffer failed", mName);
+        return WOULD_BLOCK;
+    }
+    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->clear();
+    }
+}
+
+void OutputBuffersArray::getArray(Vector<sp<MediaCodecBuffer>> *array) const {
+    mImpl.getArray(array);
+}
+
+void OutputBuffersArray::realloc(const std::shared_ptr<C2Buffer> &c2buffer) {
+    std::function<sp<Codec2Buffer>()> alloc;
+    switch (c2buffer->data().type()) {
+        case C2BufferData::LINEAR: {
+            uint32_t size = kLinearBufferSize;
+            const C2ConstLinearBlock &block = c2buffer->data().linearBlocks().front();
+            if (block.size() < kMaxLinearBufferSize / 2) {
+                size = block.size() * 2;
+            } else {
+                size = kMaxLinearBufferSize;
+            }
+            alloc = [format = mFormat, size] {
+                return new LocalLinearBuffer(format, new ABuffer(size));
+            };
+            break;
+        }
+
+        // TODO: add support
+        case C2BufferData::GRAPHIC:         [[fallthrough]];
+
+        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(alloc);
+}
+
+size_t OutputBuffersArray::numClientBuffers() const {
+    return mImpl.numClientBuffers();
+}
+
+// FlexOutputBuffers
+
+status_t FlexOutputBuffers::registerBuffer(
+        const std::shared_ptr<C2Buffer> &buffer,
+        size_t *index,
+        sp<MediaCodecBuffer> *clientBuffer) {
+    sp<Codec2Buffer> newBuffer = wrap(buffer);
+    if (newBuffer == nullptr) {
+        return NO_MEMORY;
+    }
+    newBuffer->setFormat(mFormat);
+    *index = mImpl.assignSlot(newBuffer);
+    handleImageData(newBuffer);
+    *clientBuffer = newBuffer;
+    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<OutputBuffers> FlexOutputBuffers::toArrayMode(size_t size) {
+    std::unique_ptr<OutputBuffersArray> array(new OutputBuffersArray(mComponentName.c_str()));
+    array->setFormat(mFormat);
+    array->transferSkipCutBuffer(mSkipCutBuffer);
+    array->initialize(
+            mImpl,
+            size,
+            [this]() { return allocateArrayBuffer(); });
+    return std::move(array);
+}
+
+size_t FlexOutputBuffers::numClientBuffers() const {
+    return mImpl.numClientBuffers();
+}
+
+// LinearOutputBuffers
+
+void LinearOutputBuffers::flush(
+        const std::list<std::unique_ptr<C2Work>> &flushedWork) {
+    if (mSkipCutBuffer != nullptr) {
+        mSkipCutBuffer->clear();
+    }
+    FlexOutputBuffers::flush(flushedWork);
+}
+
+sp<Codec2Buffer> LinearOutputBuffers::wrap(const std::shared_ptr<C2Buffer> &buffer) {
+    if (buffer == nullptr) {
+        ALOGV("[%s] using a dummy buffer", mName);
+        return new LocalLinearBuffer(mFormat, new ABuffer(0));
+    }
+    if (buffer->data().type() != C2BufferData::LINEAR) {
+        ALOGV("[%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) {
+        ALOGV("[%s] no linear buffers", mName);
+        // We expect one and only one linear block from the component.
+        return nullptr;
+    }
+    sp<Codec2Buffer> clientBuffer = ConstLinearBlockBuffer::Allocate(mFormat, buffer);
+    if (clientBuffer == nullptr) {
+        ALOGD("[%s] ConstLinearBlockBuffer::Allocate failed", mName);
+        return nullptr;
+    }
+    submit(clientBuffer);
+    return clientBuffer;
+}
+
+sp<Codec2Buffer> LinearOutputBuffers::allocateArrayBuffer() {
+    // TODO: proper max output size
+    return new LocalLinearBuffer(mFormat, new ABuffer(kLinearBufferSize));
+}
+
+// GraphicOutputBuffers
+
+sp<Codec2Buffer> GraphicOutputBuffers::wrap(const std::shared_ptr<C2Buffer> &buffer) {
+    return new DummyContainerBuffer(mFormat, buffer);
+}
+
+sp<Codec2Buffer> GraphicOutputBuffers::allocateArrayBuffer() {
+    return new DummyContainerBuffer(mFormat);
+}
+
+// RawGraphicOutputBuffers
+
+RawGraphicOutputBuffers::RawGraphicOutputBuffers(
+        size_t numOutputSlots, const char *componentName, const char *name)
+    : FlexOutputBuffers(componentName, name),
+      mLocalBufferPool(LocalBufferPool::Create(
+              kMaxLinearBufferSize * numOutputSlots)) { }
+
+sp<Codec2Buffer> RawGraphicOutputBuffers::wrap(const std::shared_ptr<C2Buffer> &buffer) {
+    if (buffer == nullptr) {
+        sp<Codec2Buffer> c2buffer = ConstGraphicBlockBuffer::AllocateEmpty(
+                mFormat,
+                [lbp = mLocalBufferPool](size_t capacity) {
+                    return lbp->newBuffer(capacity);
+                });
+        if (c2buffer == nullptr) {
+            ALOGD("[%s] ConstGraphicBlockBuffer::AllocateEmpty failed", mName);
+            return nullptr;
+        }
+        c2buffer->setRange(0, 0);
+        return c2buffer;
+    } else {
+        return ConstGraphicBlockBuffer::Allocate(
+                mFormat,
+                buffer,
+                [lbp = mLocalBufferPool](size_t capacity) {
+                    return lbp->newBuffer(capacity);
+                });
+    }
+}
+
+sp<Codec2Buffer> RawGraphicOutputBuffers::allocateArrayBuffer() {
+    return ConstGraphicBlockBuffer::AllocateEmpty(
+            mFormat,
+            [lbp = mLocalBufferPool](size_t capacity) {
+                return lbp->newBuffer(capacity);
+            });
+}
+
+}  // namespace android