CCodec: Episode IV --- Raw Video Buffers

Define and handle new buffer types for raw video buffers.

Bug: 69376489
Test: adb shell setprop debug.stagefright.ccodec yes
Test: adb shell setprop debug.stagefright.omx_default_rank 1000
Test: adb shell killall mediaserver
Test: atest CtsMediaTestCases:ImageReaderDecoderTest#testOtherH264ImageReader
Change-Id: I9d1061287e4c46526170ce395f826eccf4def4b7
(cherry picked from commit 156398391ed8d14b527328cb276922168c8d91a8)
diff --git a/media/libstagefright/BufferImpl.cpp b/media/libstagefright/BufferImpl.cpp
index d2eee33..7b3fa02 100644
--- a/media/libstagefright/BufferImpl.cpp
+++ b/media/libstagefright/BufferImpl.cpp
@@ -70,6 +70,10 @@
     if (const_cast<Codec2Buffer *>(this)->base() == nullptr) {
         return false;
     }
+    if (!buffer) {
+        // Nothing to copy, so we can copy by doing nothing.
+        return true;
+    }
     if (buffer->data().type() != C2BufferData::LINEAR) {
         return false;
     }
@@ -89,7 +93,7 @@
 
 bool Codec2Buffer::copyLinear(const std::shared_ptr<C2Buffer> &buffer) {
     // We assume that all canCopyLinear() checks passed.
-    if (buffer->data().linearBlocks().size() == 0u) {
+    if (!buffer || buffer->data().linearBlocks().size() == 0u) {
         setRange(0, 0);
         return true;
     }
@@ -207,4 +211,445 @@
     return std::move(mBufferRef);
 }
 
+// GraphicView2MediaImageConverter
+
+namespace {
+
+class GraphicView2MediaImageConverter {
+public:
+    explicit GraphicView2MediaImageConverter(const C2GraphicView &view)
+        : mInitCheck(NO_INIT),
+          mView(view),
+          mWidth(view.width()),
+          mHeight(view.height()),
+          mAllocatedDepth(0),
+          mBackBufferSize(0),
+          mMediaImage(new ABuffer(sizeof(MediaImage2))) {
+        if (view.error() != C2_OK) {
+            ALOGD("Converter: view.error() = %d", view.error());
+            mInitCheck = BAD_VALUE;
+            return;
+        }
+        MediaImage2 *mediaImage = (MediaImage2 *)mMediaImage->base();
+        const C2PlanarLayout &layout = view.layout();
+        if (layout.numPlanes == 0) {
+            ALOGD("Converter: 0 planes");
+            mInitCheck = BAD_VALUE;
+            return;
+        }
+        mAllocatedDepth = layout.planes[0].allocatedDepth;
+        uint32_t bitDepth = layout.planes[0].bitDepth;
+
+        switch (layout.type) {
+            case C2PlanarLayout::TYPE_YUV:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUV; break;
+            case C2PlanarLayout::TYPE_YUVA:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUVA; break;
+            case C2PlanarLayout::TYPE_RGB:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGB; break;
+            case C2PlanarLayout::TYPE_RGBA:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGBA; break;
+            default:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_UNKNOWN; break;
+        }
+        mediaImage->mNumPlanes = layout.numPlanes;
+        mediaImage->mWidth = mWidth;
+        mediaImage->mHeight = mHeight;
+        mediaImage->mBitDepth = bitDepth;
+        mediaImage->mBitDepthAllocated = mAllocatedDepth;
+
+        uint32_t bufferSize = 0;
+        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            if (plane.rightShift != 0) {
+                ALOGV("rightShift value of %u unsupported", plane.rightShift);
+                mInitCheck = BAD_VALUE;
+                return;
+            }
+            if (plane.endianness != C2PlaneInfo::NATIVE) {
+                ALOGV("endianness value of %u unsupported", plane.endianness);
+                mInitCheck = BAD_VALUE;
+                return;
+            }
+            if (plane.allocatedDepth != mAllocatedDepth || plane.bitDepth != bitDepth) {
+                ALOGV("different allocatedDepth/bitDepth per plane unsupported");
+                mInitCheck = BAD_VALUE;
+                return;
+            }
+            bufferSize += mWidth * mHeight
+                    / plane.rowSampling / plane.colSampling * (plane.allocatedDepth / 8);
+        }
+
+        mBackBufferSize = bufferSize;
+        mInitCheck = OK;
+    }
+
+    status_t initCheck() const { return mInitCheck; }
+
+    uint32_t backBufferSize() const { return mBackBufferSize; }
+
+    /**
+     * Convert C2GraphicView to MediaImage2. Note that if not wrapped, the content
+     * is not copied over in this function --- the caller should use
+     * CopyGraphicView2MediaImage() function to do that explicitly.
+     *
+     * \param   view[in]          source C2GraphicView object.
+     * \param   alloc[in]         allocator function for ABuffer.
+     * \param   mediaImage[out]   destination MediaImage2 object.
+     * \param   buffer[out]       new buffer object.
+     * \param   wrapped[out]      whether we wrapped around existing map or
+     *                            allocated a new buffer
+     *
+     * \return  true              if conversion succeeds,
+     *          false             otherwise; all output params should be ignored.
+     */
+    sp<ABuffer> wrap() {
+        MediaImage2 *mediaImage = getMediaImage();
+        const C2PlanarLayout &layout = mView.layout();
+        if (layout.numPlanes == 1) {
+            const C2PlaneInfo &plane = layout.planes[0];
+            ssize_t offset = plane.minOffset(mWidth, mHeight);
+            mediaImage->mPlane[0].mOffset = -offset;
+            mediaImage->mPlane[0].mColInc = plane.colInc;
+            mediaImage->mPlane[0].mRowInc = plane.rowInc;
+            mediaImage->mPlane[0].mHorizSubsampling = plane.colSampling;
+            mediaImage->mPlane[0].mVertSubsampling = plane.rowSampling;
+            return new ABuffer(
+                    const_cast<uint8_t *>(mView.data()[0] + offset),
+                    plane.maxOffset(mWidth, mHeight) - offset + 1);
+        }
+        const uint8_t *minPtr = mView.data()[0];
+        const uint8_t *maxPtr = mView.data()[0];
+        int32_t planeSize = 0;
+        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            ssize_t minOffset = plane.minOffset(mWidth, mHeight);
+            ssize_t maxOffset = plane.maxOffset(mWidth, mHeight);
+            if (minPtr > mView.data()[i] + minOffset) {
+                minPtr = mView.data()[i] + minOffset;
+            }
+            if (maxPtr < mView.data()[i] + maxOffset) {
+                maxPtr = mView.data()[i] + maxOffset;
+            }
+            planeSize += std::abs(plane.rowInc) * mHeight
+                    / plane.rowSampling / plane.colSampling * (mAllocatedDepth / 8);
+        }
+
+        if ((maxPtr - minPtr + 1) <= planeSize) {
+            // FIXME: this is risky as reading/writing data out of bound results in
+            //        an undefined behavior.
+            for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+                const C2PlaneInfo &plane = layout.planes[i];
+                mediaImage->mPlane[i].mOffset = mView.data()[i] - minPtr;
+                mediaImage->mPlane[i].mColInc = plane.colInc;
+                mediaImage->mPlane[i].mRowInc = plane.rowInc;
+                mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
+                mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
+            }
+            return new ABuffer(const_cast<uint8_t *>(minPtr), maxPtr - minPtr + 1);
+        }
+
+        return nullptr;
+    }
+
+    bool setBackBuffer(const sp<ABuffer> &backBuffer) {
+        if (backBuffer->capacity() < mBackBufferSize) {
+            return false;
+        }
+        backBuffer->setRange(0, mBackBufferSize);
+
+        const C2PlanarLayout &layout = mView.layout();
+        MediaImage2 *mediaImage = getMediaImage();
+        uint32_t offset = 0;
+        // TODO: keep interleaved planes together
+        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            mediaImage->mPlane[i].mOffset = offset;
+            mediaImage->mPlane[i].mColInc = mAllocatedDepth / 8;
+            mediaImage->mPlane[i].mRowInc =
+                mediaImage->mPlane[i].mColInc * mWidth / plane.colSampling;
+            mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
+            mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
+            offset += mediaImage->mPlane[i].mRowInc * mHeight / plane.rowSampling;
+        }
+        mBackBuffer = backBuffer;
+        return true;
+    }
+
+    /**
+     * Copy C2GraphicView to MediaImage2. This function assumes that |mediaImage| is
+     * an output from GraphicView2MediaImage(), so it mostly skips sanity check.
+     *
+     * \param   view[in]          source C2GraphicView object.
+     * \param   mediaImage[in]    destination MediaImage2 object.
+     * \param   buffer[out]       new buffer object.
+     */
+    void copy() {
+        // TODO: more efficient copying --- e.g. one row at a time, copying
+        //       interleaved planes together, etc.
+        const C2PlanarLayout &layout = mView.layout();
+        MediaImage2 *mediaImage = getMediaImage();
+        uint8_t *dst = mBackBuffer->base();
+        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            const uint8_t *src = mView.data()[i];
+            int32_t planeW = mWidth / plane.colSampling;
+            int32_t planeH = mHeight / plane.rowSampling;
+            for (int32_t row = 0; row < planeH; ++row) {
+                for(int32_t col = 0; col < planeW; ++col) {
+                    memcpy(dst, src, mAllocatedDepth / 8);
+                    dst += mediaImage->mPlane[i].mColInc;
+                    src += plane.colInc;
+                }
+                dst -= mediaImage->mPlane[i].mColInc * planeW;
+                dst += mediaImage->mPlane[i].mRowInc;
+                src -= plane.colInc * planeW;
+                src += plane.rowInc;
+            }
+        }
+    }
+
+    const sp<ABuffer> &imageData() const { return mMediaImage; }
+
+private:
+    status_t mInitCheck;
+
+    const C2GraphicView mView;
+    uint32_t mWidth;
+    uint32_t mHeight;
+    uint32_t mAllocatedDepth;
+    uint32_t mBackBufferSize;
+    sp<ABuffer> mMediaImage;
+    std::function<sp<ABuffer>(size_t)> mAlloc;
+
+    sp<ABuffer> mBackBuffer;
+
+    MediaImage2 *getMediaImage() {
+        return (MediaImage2 *)mMediaImage->base();
+    }
+};
+
+}  // namespace
+
+// GraphicBlockBuffer
+
+// static
+sp<GraphicBlockBuffer> GraphicBlockBuffer::Allocate(
+        const sp<AMessage> &format,
+        const std::shared_ptr<C2GraphicBlock> &block,
+        std::function<sp<ABuffer>(size_t)> alloc) {
+    C2GraphicView view(block->map().get());
+    if (view.error() != C2_OK) {
+        ALOGD("C2GraphicBlock::map failed: %d", view.error());
+        return nullptr;
+    }
+    GraphicView2MediaImageConverter converter(view);
+    if (converter.initCheck() != OK) {
+        ALOGD("Converter init failed: %d", converter.initCheck());
+        return nullptr;
+    }
+    bool wrapped = true;
+    sp<ABuffer> buffer = converter.wrap();
+    if (buffer == nullptr) {
+        buffer = alloc(converter.backBufferSize());
+        if (!converter.setBackBuffer(buffer)) {
+            ALOGD("Converter failed to set back buffer");
+            return nullptr;
+        }
+        wrapped = false;
+    }
+    return new GraphicBlockBuffer(
+            format,
+            buffer,
+            std::move(view),
+            block,
+            converter.imageData(),
+            wrapped);
+}
+
+GraphicBlockBuffer::GraphicBlockBuffer(
+        const sp<AMessage> &format,
+        const sp<ABuffer> &buffer,
+        C2GraphicView &&view,
+        const std::shared_ptr<C2GraphicBlock> &block,
+        const sp<ABuffer> &imageData,
+        bool wrapped)
+    : Codec2Buffer(format, buffer),
+      mView(view),
+      mBlock(block),
+      mImageData(imageData),
+      mWrapped(wrapped) {
+    meta()->setBuffer("image-data", imageData);
+}
+
+std::shared_ptr<C2Buffer> GraphicBlockBuffer::asC2Buffer() {
+    uint32_t width = mView.width();
+    uint32_t height = mView.height();
+    if (!mWrapped) {
+        MediaImage2 *mediaImage = imageData();
+        const C2PlanarLayout &layout = mView.layout();
+        for (uint32_t i = 0; i < mediaImage->mNumPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            int32_t planeW = width / plane.colSampling;
+            int32_t planeH = height / plane.rowSampling;
+            const uint8_t *src = base() + mediaImage->mPlane[i].mOffset;
+            uint8_t *dst = mView.data()[i];
+            for (int32_t row = 0; row < planeH; ++row) {
+                for (int32_t col = 0; col < planeW; ++col) {
+                    memcpy(dst, src, mediaImage->mBitDepthAllocated / 8);
+                    src += mediaImage->mPlane[i].mColInc;
+                    dst += plane.colInc;
+                }
+                src -= mediaImage->mPlane[i].mColInc * planeW;
+                dst -= plane.colInc * planeW;
+                src += mediaImage->mPlane[i].mRowInc;
+                dst += plane.rowInc;
+            }
+        }
+    }
+    return C2Buffer::CreateGraphicBuffer(
+            mBlock->share(C2Rect(width, height), C2Fence()));
+}
+
+// ConstGraphicBlockBuffer
+
+// static
+sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::Allocate(
+        const sp<AMessage> &format,
+        const std::shared_ptr<C2Buffer> &buffer,
+        std::function<sp<ABuffer>(size_t)> alloc) {
+    if (!buffer
+            || buffer->data().type() != C2BufferData::GRAPHIC
+            || buffer->data().graphicBlocks().size() != 1u) {
+        ALOGD("C2Buffer precond fail");
+        return nullptr;
+    }
+    std::unique_ptr<const C2GraphicView> view(std::make_unique<const C2GraphicView>(
+            buffer->data().graphicBlocks()[0].map().get()));
+    std::unique_ptr<const C2GraphicView> holder;
+
+    GraphicView2MediaImageConverter converter(*view);
+    if (converter.initCheck() != OK) {
+        ALOGD("Converter init failed: %d", converter.initCheck());
+        return nullptr;
+    }
+    bool wrapped = true;
+    sp<ABuffer> aBuffer = converter.wrap();
+    if (aBuffer == nullptr) {
+        aBuffer = alloc(converter.backBufferSize());
+        if (!converter.setBackBuffer(aBuffer)) {
+            ALOGD("Converter failed to set back buffer");
+            return nullptr;
+        }
+        wrapped = false;
+        converter.copy();
+        // We don't need the view.
+        holder = std::move(view);
+    }
+    return new ConstGraphicBlockBuffer(
+            format,
+            aBuffer,
+            std::move(view),
+            buffer,
+            converter.imageData(),
+            wrapped);
+}
+
+// static
+sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::AllocateEmpty(
+        const sp<AMessage> &format,
+        std::function<sp<ABuffer>(size_t)> alloc) {
+    int32_t width, height;
+    if (!format->findInt32("width", &width)
+            || !format->findInt32("height", &height)) {
+        ALOGD("format had no width / height");
+        return nullptr;
+    }
+    sp<ABuffer> aBuffer(alloc(width * height * 4));
+    return new ConstGraphicBlockBuffer(
+            format,
+            aBuffer,
+            nullptr,
+            nullptr,
+            nullptr,
+            false);
+}
+
+ConstGraphicBlockBuffer::ConstGraphicBlockBuffer(
+        const sp<AMessage> &format,
+        const sp<ABuffer> &aBuffer,
+        std::unique_ptr<const C2GraphicView> &&view,
+        const std::shared_ptr<C2Buffer> &buffer,
+        const sp<ABuffer> &imageData,
+        bool wrapped)
+    : Codec2Buffer(format, aBuffer),
+      mView(std::move(view)),
+      mBufferRef(buffer),
+      mWrapped(wrapped) {
+    if (imageData != nullptr) {
+        meta()->setBuffer("image-data", imageData);
+    }
+}
+
+std::shared_ptr<C2Buffer> ConstGraphicBlockBuffer::asC2Buffer() {
+    mView.reset();
+    return std::move(mBufferRef);
+}
+
+bool ConstGraphicBlockBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
+    if (mWrapped || mBufferRef) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: %swrapped ; buffer ref %s",
+                mWrapped ? "" : "not ", mBufferRef ? "exists" : "doesn't exist");
+        return false;
+    }
+    if (!buffer) {
+        // Nothing to copy, so we can copy by doing nothing.
+        return true;
+    }
+    if (buffer->data().type() != C2BufferData::GRAPHIC) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: buffer precondition unsatisfied");
+        return false;
+    }
+    if (buffer->data().graphicBlocks().size() == 0) {
+        return true;
+    } else if (buffer->data().graphicBlocks().size() != 1u) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: too many blocks");
+        return false;
+    }
+    GraphicView2MediaImageConverter converter(
+            buffer->data().graphicBlocks()[0].map().get());
+    if (converter.initCheck() != OK) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: converter init failed: %d", converter.initCheck());
+        return false;
+    }
+    if (converter.backBufferSize() > capacity()) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: insufficient capacity: req %u has %zu",
+                converter.backBufferSize(), capacity());
+        return false;
+    }
+    return true;
+}
+
+bool ConstGraphicBlockBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
+    if (!buffer || buffer->data().graphicBlocks().size() == 0) {
+        setRange(0, 0);
+        return true;
+    }
+    GraphicView2MediaImageConverter converter(
+            buffer->data().graphicBlocks()[0].map().get());
+    if (converter.initCheck() != OK) {
+        ALOGD("ConstGraphicBlockBuffer::copy: converter init failed: %d", converter.initCheck());
+        return false;
+    }
+    sp<ABuffer> aBuffer = new ABuffer(base(), capacity());
+    if (!converter.setBackBuffer(aBuffer)) {
+        ALOGD("ConstGraphicBlockBuffer::copy: set back buffer failed");
+        return false;
+    }
+    converter.copy();
+    meta()->setBuffer("image-data", converter.imageData());
+    mBufferRef = buffer;
+    return true;
+}
+
 }  // namespace android
diff --git a/media/libstagefright/CCodec.cpp b/media/libstagefright/CCodec.cpp
index a144382..0bdd808 100644
--- a/media/libstagefright/CCodec.cpp
+++ b/media/libstagefright/CCodec.cpp
@@ -404,6 +404,14 @@
             if (audio) {
                 outputFormat->setInt32("channel-count", 2);
                 outputFormat->setInt32("sample-rate", 44100);
+            } else {
+                int32_t tmp;
+                if (msg->findInt32("width", &tmp)) {
+                    outputFormat->setInt32("width", tmp);
+                }
+                if (msg->findInt32("height", &tmp)) {
+                    outputFormat->setInt32("height", tmp);
+                }
             }
         }
 
diff --git a/media/libstagefright/CCodecBufferChannel.cpp b/media/libstagefright/CCodecBufferChannel.cpp
index de78c80..65d637b 100644
--- a/media/libstagefright/CCodecBufferChannel.cpp
+++ b/media/libstagefright/CCodecBufferChannel.cpp
@@ -182,21 +182,158 @@
 const static size_t kMinBufferArraySize = 16;
 const static size_t kLinearBufferSize = 524288;
 
-sp<LinearBlockBuffer> allocateLinearBuffer(
+/**
+ * Simple local buffer pool backed by std::vector.
+ */
+class LocalBufferPool : public std::enable_shared_from_this<LocalBufferPool> {
+public:
+    /**
+     * Create a new LocalBufferPool object.
+     *
+     * \param poolCapacity  max total size of buffers managed by this pool.
+     *
+     * \return  a newly created pool object.
+     */
+    static std::shared_ptr<LocalBufferPool> Create(size_t poolCapacity) {
+        return std::shared_ptr<LocalBufferPool>(new LocalBufferPool(poolCapacity));
+    }
+
+    /**
+     * Return an ABuffer object whose size is at least |capacity|.
+     *
+     * \param   capacity  requested capacity
+     * \return  nullptr if the pool capacity is reached
+     *          an ABuffer object otherwise.
+     */
+    sp<ABuffer> 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());
+    }
+
+private:
+    /**
+     * ABuffer backed by std::vector.
+     */
+    class VectorBuffer : public ::android::ABuffer {
+    public:
+        /**
+         * Construct a VectorBuffer by taking the ownership of supplied vector.
+         *
+         * \param vec   backing vector of the buffer. this object takes
+         *              ownership at construction.
+         * \param pool  a LocalBufferPool object to return the vector at
+         *              destruction.
+         */
+        VectorBuffer(std::vector<uint8_t> &&vec, const std::shared_ptr<LocalBufferPool> &pool)
+            : ABuffer(vec.data(), vec.capacity()),
+              mVec(std::move(vec)),
+              mPool(pool) {
+        }
+
+        ~VectorBuffer() override {
+            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));
+            }
+        }
+
+    private:
+        std::vector<uint8_t> mVec;
+        std::weak_ptr<LocalBufferPool> mPool;
+    };
+
+    Mutex mMutex;
+    size_t mPoolCapacity;
+    size_t mUsedSize;
+    std::list<std::vector<uint8_t>> mPool;
+
+    /**
+     * Private constructor to prevent constructing non-managed LocalBufferPool.
+     */
+    explicit LocalBufferPool(size_t poolCapacity)
+        : mPoolCapacity(poolCapacity), mUsedSize(0) {
+    }
+
+    /**
+     * Take back the ownership of vec from the destructed VectorBuffer and put
+     * it in front of the pool.
+     */
+    void returnVector(std::vector<uint8_t> &&vec) {
+        Mutex::Autolock lock(mMutex);
+        mPool.push_front(std::move(vec));
+    }
+
+    DISALLOW_EVIL_CONSTRUCTORS(LocalBufferPool);
+};
+
+sp<LinearBlockBuffer> AllocateLinearBuffer(
         const std::shared_ptr<C2BlockPool> &pool,
         const sp<AMessage> &format,
         size_t size,
         const C2MemoryUsage &usage) {
     std::shared_ptr<C2LinearBlock> block;
 
-    status_t err = pool->fetchLinearBlock(size, usage, &block);
-    if (err != OK) {
+    c2_status_t err = pool->fetchLinearBlock(size, usage, &block);
+    if (err != C2_OK) {
         return nullptr;
     }
 
     return LinearBlockBuffer::Allocate(format, block);
 }
 
+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);
+            });
+}
+
 class BuffersArrayImpl;
 
 /**
@@ -386,6 +523,7 @@
 class InputBuffersArray : public CCodecBufferChannel::InputBuffers {
 public:
     InputBuffersArray() = default;
+    ~InputBuffersArray() override = default;
 
     void initialize(
             const FlexBuffersImpl &impl,
@@ -435,7 +573,8 @@
         // TODO: proper max input size
         // TODO: read usage from intf
         C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
-        sp<LinearBlockBuffer> newBuffer = allocateLinearBuffer(mPool, mFormat, kLinearBufferSize, usage);
+        sp<LinearBlockBuffer> newBuffer = AllocateLinearBuffer(
+                mPool, mFormat, kLinearBufferSize, usage);
         if (newBuffer == nullptr) {
             return false;
         }
@@ -461,7 +600,7 @@
                 kMinBufferArraySize,
                 [pool = mPool, format = mFormat] () -> sp<Codec2Buffer> {
                     C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
-                    return allocateLinearBuffer(pool, format, kLinearBufferSize, usage);
+                    return AllocateLinearBuffer(pool, format, kLinearBufferSize, usage);
                 });
         return std::move(array);
     }
@@ -472,7 +611,54 @@
 
 class GraphicInputBuffers : public CCodecBufferChannel::InputBuffers {
 public:
-    GraphicInputBuffers() = default;
+    GraphicInputBuffers() : mLocalBufferPool(LocalBufferPool::Create(1920 * 1080 * 16)) {}
+    ~GraphicInputBuffers() override = default;
+
+    bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override {
+        // 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);
+        *buffer = newBuffer;
+        return true;
+    }
+
+    std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) override {
+        return mImpl.releaseSlot(buffer);
+    }
+
+    void flush() override {
+        // 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<CCodecBufferChannel::InputBuffers> toArrayMode() final {
+        std::unique_ptr<InputBuffersArray> array(new InputBuffersArray);
+        array->setFormat(mFormat);
+        array->initialize(
+                mImpl,
+                kMinBufferArraySize,
+                [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);
+    }
+
+private:
+    FlexBuffersImpl mImpl;
+    std::shared_ptr<LocalBufferPool> mLocalBufferPool;
+};
+
+class DummyInputBuffers : public CCodecBufferChannel::InputBuffers {
+public:
+    DummyInputBuffers() = default;
 
     bool requestNewBuffer(size_t *, sp<MediaCodecBuffer> *) override {
         return false;
@@ -498,7 +684,8 @@
 
 class OutputBuffersArray : public CCodecBufferChannel::OutputBuffers {
 public:
-    using CCodecBufferChannel::OutputBuffers::OutputBuffers;
+    OutputBuffersArray() = default;
+    ~OutputBuffersArray() override = default;
 
     void initialize(
             const FlexBuffersImpl &impl,
@@ -525,12 +712,14 @@
                     return clientBuffer->canCopy(buffer);
                 });
         if (err != OK) {
-            return false;
-        }
-        if (!c2Buffer->copy(buffer)) {
+            ALOGD("grabBuffer failed: %d", err);
             return false;
         }
         c2Buffer->setFormat(mFormat);
+        if (!c2Buffer->copy(buffer)) {
+            ALOGD("copy buffer failed");
+            return false;
+        }
         *clientBuffer = c2Buffer;
         return true;
     }
@@ -631,8 +820,21 @@
         return std::move(array);
     }
 
+    /**
+     * Return an appropriate Codec2Buffer object for the type of buffers.
+     *
+     * \param buffer  C2Buffer object to wrap.
+     *
+     * \return  appropriate Codec2Buffer object to wrap |buffer|.
+     */
     virtual sp<Codec2Buffer> wrap(const std::shared_ptr<C2Buffer> &buffer) = 0;
 
+    /**
+     * Return an appropriate Codec2Buffer object for the type of buffers, to be
+     * used as an empty array buffer.
+     *
+     * \return  appropriate Codec2Buffer object which can copy() from C2Buffers.
+     */
     virtual sp<Codec2Buffer> allocateArrayBuffer() = 0;
 
 private:
@@ -677,6 +879,34 @@
     }
 };
 
+class RawGraphicOutputBuffers : public FlexOutputBuffers {
+public:
+    RawGraphicOutputBuffers()
+        : mLocalBufferPool(LocalBufferPool::Create(1920 * 1080 * 16)) {
+    }
+    ~RawGraphicOutputBuffers() override = default;
+
+    sp<Codec2Buffer> wrap(const std::shared_ptr<C2Buffer> &buffer) override {
+        return ConstGraphicBlockBuffer::Allocate(
+                mFormat,
+                buffer,
+                [lbp = mLocalBufferPool](size_t capacity) {
+                    return lbp->newBuffer(capacity);
+                });
+    }
+
+    sp<Codec2Buffer> allocateArrayBuffer() override {
+        return ConstGraphicBlockBuffer::AllocateEmpty(
+                mFormat,
+                [lbp = mLocalBufferPool](size_t capacity) {
+                    return lbp->newBuffer(capacity);
+                });
+    }
+
+private:
+    std::shared_ptr<LocalBufferPool> mLocalBufferPool;
+};
+
 }  // namespace
 
 CCodecBufferChannel::QueueGuard::QueueGuard(
@@ -942,7 +1172,11 @@
 
         bool graphic = (iStreamFormat.value == C2FormatVideo);
         if (graphic) {
-            buffers->reset(new GraphicInputBuffers);
+            if (mInputSurface) {
+                buffers->reset(new DummyInputBuffers);
+            } else {
+                buffers->reset(new GraphicInputBuffers);
+            }
         } else {
             buffers->reset(new LinearInputBuffers);
         }
@@ -964,11 +1198,21 @@
     }
 
     if (outputFormat != nullptr) {
+        bool hasOutputSurface = false;
+        {
+            Mutexed<sp<Surface>>::Locked surface(mSurface);
+            hasOutputSurface = (*surface != nullptr);
+        }
+
         Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
 
         bool graphic = (oStreamFormat.value == C2FormatVideo);
         if (graphic) {
-            buffers->reset(new GraphicOutputBuffers);
+            if (hasOutputSurface) {
+                buffers->reset(new GraphicOutputBuffers);
+            } else {
+                buffers->reset(new RawGraphicOutputBuffers);
+            }
         } else {
             buffers->reset(new LinearOutputBuffers);
         }
diff --git a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
index 9b39ae9..1fc2c8c 100644
--- a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
+++ b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
@@ -37,15 +37,6 @@
 
 #include "ih264d_defs.h"
 
-namespace {
-
-template <class T>
-inline int32_t floor32(T arg) {
-   return (int32_t) std::llround(std::floor(arg));
-}
-
-} // namespace
-
 namespace android {
 
 struct iv_obj_t : public ::iv_obj_t {};
@@ -83,6 +74,20 @@
             .build();
 }
 
+void CopyPlane(
+        uint8_t *dst, const C2PlaneInfo &plane,
+        const uint8_t *src, uint32_t width, uint32_t height) {
+    for (uint32_t row = 0; row < height; ++row) {
+        for (uint32_t col = 0; col < width; ++col) {
+            *dst = *src;
+            dst += plane.colInc;
+            ++src;
+        }
+        dst -= plane.colInc * width;
+        dst += plane.rowInc;
+    }
+}
+
 void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
     uint32_t flags = 0;
     if ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM)) {
@@ -103,7 +108,7 @@
         c2_node_id_t id)
     : SimpleC2Component(BuildIntf(name, id)),
       mCodecCtx(NULL),
-      mFlushOutBuffer(NULL),
+      mOutBuffer(NULL),
       mIvColorFormat(IV_YUV_420P),
       mChangingResolution(false),
       mSignalledError(false),
@@ -164,9 +169,9 @@
         }
     }
 
-    if (mFlushOutBuffer) {
-        free(mFlushOutBuffer);
-        mFlushOutBuffer = NULL;
+    if (mOutBuffer) {
+        free(mOutBuffer);
+        mOutBuffer = NULL;
     }
     return C2_OK;
 }
@@ -244,6 +249,16 @@
 
         return UNKNOWN_ERROR;
     }
+
+    if (mOutBuffer != NULL) {
+        free(mOutBuffer);
+    }
+    uint32_t bufferSize = mWidth * mHeight * 3 / 2;
+    mOutBuffer = (uint8_t *)memalign(128, bufferSize);
+    if (NULL == mOutBuffer) {
+        ALOGE("Could not allocate output buffer of size %u", bufferSize);
+        return C2_NO_MEMORY;
+    }
     return OK;
 }
 
@@ -321,17 +336,6 @@
         return UNKNOWN_ERROR;
     }
 
-    /* Allocate a picture buffer to flushed data */
-    uint32_t displayStride = mWidth;
-    uint32_t displayHeight = mHeight;
-
-    uint32_t bufferSize = displayStride * displayHeight * 3 / 2;
-    mFlushOutBuffer = (uint8_t *)memalign(128, bufferSize);
-    if (NULL == mFlushOutBuffer) {
-        ALOGE("Could not allocate flushOutputBuffer of size %u", bufferSize);
-        return C2_NO_MEMORY;
-    }
-
     return OK;
 }
 
@@ -499,14 +503,29 @@
                   outBuffer->width(), outBuffer->height(), width, height);
             return false;
         }
+        ALOGV("width = %u, stride[0] = %u, stride[1] = %u, stride[2] = %u",
+                outBuffer->width(),
+                outBuffer->layout().planes[0].rowInc,
+                outBuffer->layout().planes[1].rowInc,
+                outBuffer->layout().planes[2].rowInc);
+        const C2PlanarLayout &layout = outBuffer->layout();
         ps_dec_ip->s_out_buffer.pu1_bufs[0] = outBuffer->data()[0];
+        if (layout.planes[0].rowInc != (int32_t)mWidth || layout.planes[1].colInc != 1) {
+            ps_dec_ip->s_out_buffer.pu1_bufs[0] = mOutBuffer;
+        }
         ps_dec_ip->s_out_buffer.pu1_bufs[1] = outBuffer->data()[1];
+        if (layout.planes[1].rowInc != (int32_t)mWidth / 2 || layout.planes[1].colInc != 1) {
+            ps_dec_ip->s_out_buffer.pu1_bufs[1] = mOutBuffer + sizeY;
+        }
         ps_dec_ip->s_out_buffer.pu1_bufs[2] = outBuffer->data()[2];
+        if (layout.planes[2].rowInc != (int32_t)mWidth / 2 || layout.planes[2].colInc != 1) {
+            ps_dec_ip->s_out_buffer.pu1_bufs[2] = mOutBuffer + sizeY + sizeUV;
+        }
     } else {
-        // mFlushOutBuffer always has the right size.
-        ps_dec_ip->s_out_buffer.pu1_bufs[0] = mFlushOutBuffer;
-        ps_dec_ip->s_out_buffer.pu1_bufs[1] = mFlushOutBuffer + sizeY;
-        ps_dec_ip->s_out_buffer.pu1_bufs[2] = mFlushOutBuffer + sizeY + sizeUV;
+        // mOutBuffer always has the right size.
+        ps_dec_ip->s_out_buffer.pu1_bufs[0] = mOutBuffer;
+        ps_dec_ip->s_out_buffer.pu1_bufs[1] = mOutBuffer + sizeY;
+        ps_dec_ip->s_out_buffer.pu1_bufs[2] = mOutBuffer + sizeY + sizeUV;
     }
 
     ps_dec_ip->s_out_buffer.u4_num_bufs = 3;
@@ -544,7 +563,8 @@
 }
 
 void C2SoftAvcDec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &work) {
-    std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(std::move(mAllocatedBlock));
+    std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(mAllocatedBlock);
+    mAllocatedBlock.reset();
     auto fillWork = [buffer](const std::unique_ptr<C2Work> &work) {
         uint32_t flags = 0;
         if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
@@ -603,34 +623,54 @@
             break;
         }
         (void)ensureDecoderState(pool);
-        C2GraphicView output = mAllocatedBlock->map().get();
-        if (output.error() != OK) {
-            ALOGE("mapped err = %d", output.error());
-        }
-
         ivd_video_decode_ip_t s_dec_ip;
         ivd_video_decode_op_t s_dec_op;
         WORD32 timeDelay, timeTaken;
         //size_t sizeY, sizeUV;
 
-        if (!setDecodeArgs(&s_dec_ip, &s_dec_op, &input, &output, workIndex, inOffset)) {
-            ALOGE("Decoder arg setup failed");
-            // TODO: notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
-            mSignalledError = true;
-            break;
+        {
+            C2GraphicView output = mAllocatedBlock->map().get();
+            if (output.error() != C2_OK) {
+                ALOGE("mapped err = %d", output.error());
+                work->result = output.error();
+                fillEmptyWork(work);
+                return;
+            }
+            if (!setDecodeArgs(&s_dec_ip, &s_dec_op, &input, &output, workIndex, inOffset)) {
+                ALOGE("Decoder arg setup failed");
+                // TODO: notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
+                mSignalledError = true;
+                break;
+            }
+            ALOGV("Decoder arg setup succeeded");
+            // If input dump is enabled, then write to file
+            DUMP_TO_FILE(mInFile, s_dec_ip.pv_stream_buffer, s_dec_ip.u4_num_Bytes, mInputOffset);
+
+            GETTIME(&mTimeStart, NULL);
+            /* Compute time elapsed between end of previous decode()
+             * to start of current decode() */
+            TIME_DIFF(mTimeEnd, mTimeStart, timeDelay);
+
+            IV_API_CALL_STATUS_T status;
+            status = ivdec_api_function(mCodecCtx, (void *)&s_dec_ip, (void *)&s_dec_op);
+            ALOGV("status = %d, error_code = %d", status, (s_dec_op.u4_error_code & 0xFF));
+            if (s_dec_op.u4_output_present) {
+                const C2PlanarLayout &layout = output.layout();
+                if (layout.planes[0].rowInc != (int32_t)mWidth || layout.planes[1].colInc != 1) {
+                    CopyPlane(output.data()[0], layout.planes[0], mOutBuffer, mWidth, mHeight);
+                }
+                if (layout.planes[1].rowInc != (int32_t)mWidth / 2 || layout.planes[1].colInc != 1) {
+                    CopyPlane(
+                            output.data()[1], layout.planes[1],
+                            mOutBuffer + (mWidth * mHeight), mWidth / 2, mHeight / 2);
+                }
+                if (layout.planes[2].rowInc != (int32_t)mWidth / 2 || layout.planes[2].colInc != 1) {
+                    CopyPlane(
+                            output.data()[2], layout.planes[2],
+                            mOutBuffer + (mWidth * mHeight * 5 / 4), mWidth / 2, mHeight / 2);
+                }
+            }
         }
-        ALOGV("Decoder arg setup succeeded");
-        // If input dump is enabled, then write to file
-        DUMP_TO_FILE(mInFile, s_dec_ip.pv_stream_buffer, s_dec_ip.u4_num_Bytes, mInputOffset);
-
-        GETTIME(&mTimeStart, NULL);
-        /* Compute time elapsed between end of previous decode()
-         * to start of current decode() */
-        TIME_DIFF(mTimeEnd, mTimeStart, timeDelay);
-
-        IV_API_CALL_STATUS_T status;
-        status = ivdec_api_function(mCodecCtx, (void *)&s_dec_ip, (void *)&s_dec_op);
-        ALOGV("status = %d, error_code = %d", status, (s_dec_op.u4_error_code & 0xFF));
 
         bool unsupportedResolution =
             (IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED == (s_dec_op.u4_error_code & 0xFF));
diff --git a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h
index 6632bf3..d324a0f 100644
--- a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h
+++ b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h
@@ -189,7 +189,7 @@
     struct timeval mTimeEnd;     // Time at the end of decode()
 
     // Internal buffer to be used to flush out the buffers from decoder
-    uint8_t *mFlushOutBuffer;
+    uint8_t *mOutBuffer;
 
 #ifdef FILE_DUMP_ENABLE
     char mInFile[200];
diff --git a/media/libstagefright/include/Codec2Buffer.h b/media/libstagefright/include/Codec2Buffer.h
index 9766b41..eeb889d 100644
--- a/media/libstagefright/include/Codec2Buffer.h
+++ b/media/libstagefright/include/Codec2Buffer.h
@@ -20,6 +20,7 @@
 
 #include <C2Buffer.h>
 
+#include <media/hardware/VideoAPI.h>
 #include <media/MediaCodecBuffer.h>
 
 namespace android {
@@ -109,6 +110,11 @@
 public:
     /**
      * Allocate a new LinearBufferBlock wrapping around C2LinearBlock object.
+     *
+     * \param   format  mandatory buffer format for MediaCodecBuffer
+     * \param   block   C2LinearBlock object to wrap around.
+     * \return          LinearBlockBuffer object with writable mapping.
+     *                  nullptr if unsuccessful.
      */
     static sp<LinearBlockBuffer> Allocate(
             const sp<AMessage> &format, const std::shared_ptr<C2LinearBlock> &block);
@@ -137,6 +143,11 @@
 public:
     /**
      * Allocate a new ConstLinearBlockBuffer wrapping around C2Buffer object.
+     *
+     * \param   format  mandatory buffer format for MediaCodecBuffer
+     * \param   buffer  linear C2Buffer object to wrap around.
+     * \return          ConstLinearBlockBuffer object with readable mapping.
+     *                  nullptr if unsuccessful.
      */
     static sp<ConstLinearBlockBuffer> Allocate(
             const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer);
@@ -156,6 +167,110 @@
     std::shared_ptr<C2Buffer> mBufferRef;
 };
 
+/**
+ * MediaCodecBuffer implementation wraps around C2GraphicBlock.
+ *
+ * This object exposes the underlying bits via accessor APIs and "image-data"
+ * metadata, created automatically at allocation time.
+ */
+class GraphicBlockBuffer : public Codec2Buffer {
+public:
+    /**
+     * Allocate a new GraphicBlockBuffer wrapping around C2GraphicBlock object.
+     * If |block| is not in good color formats, it allocates YV12 local buffer
+     * and copies the content over at asC2Buffer().
+     *
+     * \param   format  mandatory buffer format for MediaCodecBuffer
+     * \param   block   C2GraphicBlock object to wrap around.
+     * \param   alloc   a function to allocate backing ABuffer if needed.
+     * \return          GraphicBlockBuffer object with writable mapping.
+     *                  nullptr if unsuccessful.
+     */
+    static sp<GraphicBlockBuffer> Allocate(
+            const sp<AMessage> &format,
+            const std::shared_ptr<C2GraphicBlock> &block,
+            std::function<sp<ABuffer>(size_t)> alloc);
+
+    std::shared_ptr<C2Buffer> asC2Buffer() override;
+
+    virtual ~GraphicBlockBuffer() = default;
+
+private:
+    GraphicBlockBuffer(
+            const sp<AMessage> &format,
+            const sp<ABuffer> &buffer,
+            C2GraphicView &&view,
+            const std::shared_ptr<C2GraphicBlock> &block,
+            const sp<ABuffer> &imageData,
+            bool wrapped);
+    GraphicBlockBuffer() = delete;
+
+    inline MediaImage2 *imageData() { return (MediaImage2 *)mImageData->data(); }
+
+    C2GraphicView mView;
+    std::shared_ptr<C2GraphicBlock> mBlock;
+    sp<ABuffer> mImageData;
+    const bool mWrapped;
+};
+
+/**
+ * MediaCodecBuffer implementation wraps around graphic C2Buffer object.
+ *
+ * This object exposes the underlying bits via accessor APIs and "image-data"
+ * metadata, created automatically at allocation time.
+ */
+class ConstGraphicBlockBuffer : public Codec2Buffer {
+public:
+    /**
+     * Allocate a new ConstGraphicBlockBuffer wrapping around C2Buffer object.
+     * If |buffer| is not in good color formats, it allocates YV12 local buffer
+     * and copies the content of |buffer| over to expose.
+     *
+     * \param   format  mandatory buffer format for MediaCodecBuffer
+     * \param   buffer  graphic C2Buffer object to wrap around.
+     * \param   alloc   a function to allocate backing ABuffer if needed.
+     * \return          ConstGraphicBlockBuffer object with readable mapping.
+     *                  nullptr if unsuccessful.
+     */
+    static sp<ConstGraphicBlockBuffer> Allocate(
+            const sp<AMessage> &format,
+            const std::shared_ptr<C2Buffer> &buffer,
+            std::function<sp<ABuffer>(size_t)> alloc);
+
+    /**
+     * Allocate a new ConstGraphicBlockBuffer which allocates YV12 local buffer
+     * and copies the content of |buffer| over to expose.
+     *
+     * \param   format  mandatory buffer format for MediaCodecBuffer
+     * \param   alloc   a function to allocate backing ABuffer if needed.
+     * \return          ConstGraphicBlockBuffer object with no wrapping buffer.
+     */
+    static sp<ConstGraphicBlockBuffer> AllocateEmpty(
+            const sp<AMessage> &format,
+            std::function<sp<ABuffer>(size_t)> alloc);
+
+    std::shared_ptr<C2Buffer> asC2Buffer() override;
+    bool canCopy(const std::shared_ptr<C2Buffer> &buffer) const override;
+    bool copy(const std::shared_ptr<C2Buffer> &buffer) override;
+
+    virtual ~ConstGraphicBlockBuffer() = default;
+
+private:
+    ConstGraphicBlockBuffer(
+            const sp<AMessage> &format,
+            const sp<ABuffer> &aBuffer,
+            std::unique_ptr<const C2GraphicView> &&view,
+            const std::shared_ptr<C2Buffer> &buffer,
+            const sp<ABuffer> &imageData,
+            bool wrapped);
+    ConstGraphicBlockBuffer() = delete;
+
+    sp<ABuffer> mImageData;
+    std::unique_ptr<const C2GraphicView> mView;
+    std::shared_ptr<C2Buffer> mBufferRef;
+    const bool mWrapped;
+};
+
 }  // namespace android
 
 #endif  // CODEC2_BUFFER_H_