Loading media/codec2/sfplugin/CCodecBuffers.cpp +3 −0 Original line number Diff line number Diff line Loading @@ -96,6 +96,9 @@ void CCodecBuffers::handleImageData(const sp<Codec2Buffer> &buffer) { int32_t vstride = int32_t(offsetDelta / stride); newFormat->setInt32(KEY_SLICE_HEIGHT, vstride); ALOGD("[%s] updating vstride = %d", mName, vstride); buffer->setRange( img->mPlane[0].mOffset, buffer->size() - img->mPlane[0].mOffset); } } setFormat(newFormat); Loading media/codec2/sfplugin/Codec2Buffer.cpp +8 −6 Original line number Diff line number Diff line Loading @@ -276,20 +276,22 @@ public: 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); int64_t planeStride = std::abs(plane.rowInc / plane.colInc); ssize_t minOffset = plane.minOffset( mWidth / plane.colSampling, mHeight / plane.rowSampling); ssize_t maxOffset = plane.maxOffset( mWidth / plane.colSampling, mHeight / plane.rowSampling); 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) * align(mHeight, 64) / plane.rowSampling / plane.colSampling * divUp(mAllocatedDepth, 8u); planeSize += planeStride * divUp(mAllocatedDepth, 8u) * align(mHeight, 64) / plane.rowSampling; } if ((maxPtr - minPtr + 1) <= planeSize) { if (minPtr == mView.data()[0] && (maxPtr - minPtr + 1) <= planeSize) { // FIXME: this is risky as reading/writing data out of bound results // in an undefined behavior, but gralloc does assume a // contiguous mapping Loading media/codec2/sfplugin/tests/CCodecBuffers_test.cpp +445 −5 Original line number Diff line number Diff line Loading @@ -18,22 +18,31 @@ #include <gtest/gtest.h> #include <media/stagefright/foundation/AString.h> #include <media/stagefright/MediaCodecConstants.h> #include <C2BlockInternal.h> #include <C2PlatformSupport.h> namespace android { static std::shared_ptr<RawGraphicOutputBuffers> GetRawGraphicOutputBuffers( int32_t width, int32_t height) { std::shared_ptr<RawGraphicOutputBuffers> buffers = std::make_shared<RawGraphicOutputBuffers>("test"); sp<AMessage> format{new AMessage}; format->setInt32(KEY_WIDTH, width); format->setInt32(KEY_HEIGHT, height); buffers->setFormat(format); return buffers; } TEST(RawGraphicOutputBuffersTest, ChangeNumSlots) { constexpr int32_t kWidth = 3840; constexpr int32_t kHeight = 2160; std::shared_ptr<RawGraphicOutputBuffers> buffers = std::make_shared<RawGraphicOutputBuffers>("test"); sp<AMessage> format{new AMessage}; format->setInt32("width", kWidth); format->setInt32("height", kHeight); buffers->setFormat(format); GetRawGraphicOutputBuffers(kWidth, kHeight); std::shared_ptr<C2BlockPool> pool; ASSERT_EQ(OK, GetCodec2BlockPool(C2BlockPool::BASIC_GRAPHIC, nullptr, &pool)); Loading Loading @@ -96,4 +105,435 @@ TEST(RawGraphicOutputBuffersTest, ChangeNumSlots) { } } class TestGraphicAllocation : public C2GraphicAllocation { public: TestGraphicAllocation( uint32_t width, uint32_t height, const C2PlanarLayout &layout, size_t capacity, std::vector<size_t> offsets) : C2GraphicAllocation(width, height), mLayout(layout), mMemory(capacity, 0xAA), mOffsets(offsets) { } c2_status_t map( C2Rect rect, C2MemoryUsage usage, C2Fence *fence, C2PlanarLayout *layout, uint8_t **addr) override { (void)rect; (void)usage; (void)fence; *layout = mLayout; for (size_t i = 0; i < mLayout.numPlanes; ++i) { addr[i] = mMemory.data() + mOffsets[i]; } return C2_OK; } c2_status_t unmap(uint8_t **, C2Rect, C2Fence *) override { return C2_OK; } C2Allocator::id_t getAllocatorId() const override { return -1; } const C2Handle *handle() const override { return nullptr; } bool equals(const std::shared_ptr<const C2GraphicAllocation> &other) const override { return other.get() == this; } private: C2PlanarLayout mLayout; std::vector<uint8_t> mMemory; std::vector<uint8_t *> mAddr; std::vector<size_t> mOffsets; }; class LayoutTest : public ::testing::TestWithParam<std::tuple<bool, std::string, bool, int32_t>> { private: static C2PlanarLayout YUVPlanarLayout(int32_t stride) { C2PlanarLayout layout = { C2PlanarLayout::TYPE_YUV, 3, /* numPlanes */ 3, /* rootPlanes */ {}, /* planes --- to be filled below */ }; layout.planes[C2PlanarLayout::PLANE_Y] = { C2PlaneInfo::CHANNEL_Y, 1, /* colInc */ stride, /* rowInc */ 1, /* colSampling */ 1, /* rowSampling */ 8, /* allocatedDepth */ 8, /* bitDepth */ 0, /* rightShift */ C2PlaneInfo::NATIVE, C2PlanarLayout::PLANE_Y, /* rootIx */ 0, /* offset */ }; layout.planes[C2PlanarLayout::PLANE_U] = { C2PlaneInfo::CHANNEL_CB, 1, /* colInc */ stride / 2, /* rowInc */ 2, /* colSampling */ 2, /* rowSampling */ 8, /* allocatedDepth */ 8, /* bitDepth */ 0, /* rightShift */ C2PlaneInfo::NATIVE, C2PlanarLayout::PLANE_U, /* rootIx */ 0, /* offset */ }; layout.planes[C2PlanarLayout::PLANE_V] = { C2PlaneInfo::CHANNEL_CR, 1, /* colInc */ stride / 2, /* rowInc */ 2, /* colSampling */ 2, /* rowSampling */ 8, /* allocatedDepth */ 8, /* bitDepth */ 0, /* rightShift */ C2PlaneInfo::NATIVE, C2PlanarLayout::PLANE_V, /* rootIx */ 0, /* offset */ }; return layout; } static C2PlanarLayout YUVSemiPlanarLayout(int32_t stride) { C2PlanarLayout layout = { C2PlanarLayout::TYPE_YUV, 3, /* numPlanes */ 2, /* rootPlanes */ {}, /* planes --- to be filled below */ }; layout.planes[C2PlanarLayout::PLANE_Y] = { C2PlaneInfo::CHANNEL_Y, 1, /* colInc */ stride, /* rowInc */ 1, /* colSampling */ 1, /* rowSampling */ 8, /* allocatedDepth */ 8, /* bitDepth */ 0, /* rightShift */ C2PlaneInfo::NATIVE, C2PlanarLayout::PLANE_Y, /* rootIx */ 0, /* offset */ }; layout.planes[C2PlanarLayout::PLANE_U] = { C2PlaneInfo::CHANNEL_CB, 2, /* colInc */ stride, /* rowInc */ 2, /* colSampling */ 2, /* rowSampling */ 8, /* allocatedDepth */ 8, /* bitDepth */ 0, /* rightShift */ C2PlaneInfo::NATIVE, C2PlanarLayout::PLANE_U, /* rootIx */ 0, /* offset */ }; layout.planes[C2PlanarLayout::PLANE_V] = { C2PlaneInfo::CHANNEL_CR, 2, /* colInc */ stride, /* rowInc */ 2, /* colSampling */ 2, /* rowSampling */ 8, /* allocatedDepth */ 8, /* bitDepth */ 0, /* rightShift */ C2PlaneInfo::NATIVE, C2PlanarLayout::PLANE_U, /* rootIx */ 1, /* offset */ }; return layout; } static C2PlanarLayout YVUSemiPlanarLayout(int32_t stride) { C2PlanarLayout layout = { C2PlanarLayout::TYPE_YUV, 3, /* numPlanes */ 2, /* rootPlanes */ {}, /* planes --- to be filled below */ }; layout.planes[C2PlanarLayout::PLANE_Y] = { C2PlaneInfo::CHANNEL_Y, 1, /* colInc */ stride, /* rowInc */ 1, /* colSampling */ 1, /* rowSampling */ 8, /* allocatedDepth */ 8, /* bitDepth */ 0, /* rightShift */ C2PlaneInfo::NATIVE, C2PlanarLayout::PLANE_Y, /* rootIx */ 0, /* offset */ }; layout.planes[C2PlanarLayout::PLANE_U] = { C2PlaneInfo::CHANNEL_CB, 2, /* colInc */ stride, /* rowInc */ 2, /* colSampling */ 2, /* rowSampling */ 8, /* allocatedDepth */ 8, /* bitDepth */ 0, /* rightShift */ C2PlaneInfo::NATIVE, C2PlanarLayout::PLANE_V, /* rootIx */ 1, /* offset */ }; layout.planes[C2PlanarLayout::PLANE_V] = { C2PlaneInfo::CHANNEL_CR, 2, /* colInc */ stride, /* rowInc */ 2, /* colSampling */ 2, /* rowSampling */ 8, /* allocatedDepth */ 8, /* bitDepth */ 0, /* rightShift */ C2PlaneInfo::NATIVE, C2PlanarLayout::PLANE_V, /* rootIx */ 0, /* offset */ }; return layout; } static std::shared_ptr<C2GraphicBlock> CreateGraphicBlock( uint32_t width, uint32_t height, const C2PlanarLayout &layout, size_t capacity, std::vector<size_t> offsets) { std::shared_ptr<C2GraphicAllocation> alloc = std::make_shared<TestGraphicAllocation>( width, height, layout, capacity, offsets); return _C2BlockFactory::CreateGraphicBlock(alloc); } static constexpr uint8_t GetPixelValue(uint8_t value, uint32_t row, uint32_t col) { return (uint32_t(value) * row + col) & 0xFF; } static void FillPlane(C2GraphicView &view, size_t index, uint8_t value) { C2PlanarLayout layout = view.layout(); uint8_t *rowPtr = view.data()[index]; C2PlaneInfo plane = layout.planes[index]; for (uint32_t row = 0; row < view.height() / plane.rowSampling; ++row) { uint8_t *colPtr = rowPtr; for (uint32_t col = 0; col < view.width() / plane.colSampling; ++col) { *colPtr = GetPixelValue(value, row, col); colPtr += plane.colInc; } rowPtr += plane.rowInc; } } static void FillBlock(const std::shared_ptr<C2GraphicBlock> &block) { C2GraphicView view = block->map().get(); FillPlane(view, C2PlanarLayout::PLANE_Y, 'Y'); FillPlane(view, C2PlanarLayout::PLANE_U, 'U'); FillPlane(view, C2PlanarLayout::PLANE_V, 'V'); } static bool VerifyPlane( const MediaImage2 *mediaImage, const uint8_t *base, uint32_t index, uint8_t value, std::string *errorMsg) { *errorMsg = ""; MediaImage2::PlaneInfo plane = mediaImage->mPlane[index]; const uint8_t *rowPtr = base + plane.mOffset; for (uint32_t row = 0; row < mediaImage->mHeight / plane.mVertSubsampling; ++row) { const uint8_t *colPtr = rowPtr; for (uint32_t col = 0; col < mediaImage->mWidth / plane.mHorizSubsampling; ++col) { if (GetPixelValue(value, row, col) != *colPtr) { *errorMsg = AStringPrintf("row=%u col=%u expected=%02x actual=%02x", row, col, GetPixelValue(value, row, col), *colPtr).c_str(); return false; } colPtr += plane.mColInc; } rowPtr += plane.mRowInc; } return true; } public: static constexpr int32_t kWidth = 320; static constexpr int32_t kHeight = 240; static constexpr int32_t kGapLength = kWidth * kHeight * 10; static std::shared_ptr<C2Buffer> CreateAndFillBufferFromParam(const ParamType ¶m) { bool contiguous = std::get<0>(param); std::string planeOrderStr = std::get<1>(param); bool planar = std::get<2>(param); int32_t stride = std::get<3>(param); C2PlanarLayout::plane_index_t planeOrder[3]; C2PlanarLayout layout; if (planeOrderStr.size() != 3) { return nullptr; } for (size_t i = 0; i < 3; ++i) { C2PlanarLayout::plane_index_t planeIndex; switch (planeOrderStr[i]) { case 'Y': planeIndex = C2PlanarLayout::PLANE_Y; break; case 'U': planeIndex = C2PlanarLayout::PLANE_U; break; case 'V': planeIndex = C2PlanarLayout::PLANE_V; break; default: return nullptr; } planeOrder[i] = planeIndex; } if (planar) { layout = YUVPlanarLayout(stride); } else { // semi-planar for (size_t i = 0; i < 3; ++i) { if (planeOrder[i] == C2PlanarLayout::PLANE_U) { layout = YUVSemiPlanarLayout(stride); break; } if (planeOrder[i] == C2PlanarLayout::PLANE_V) { layout = YVUSemiPlanarLayout(stride); break; } } } size_t yPlaneSize = stride * kHeight; size_t uvPlaneSize = stride * kHeight / 4; size_t capacity = yPlaneSize + uvPlaneSize * 2; std::vector<size_t> offsets(3); if (!contiguous) { if (planar) { capacity += kGapLength * 2; } else { // semi-planar capacity += kGapLength; } } offsets[planeOrder[0]] = 0; size_t planeSize = (planeOrder[0] == C2PlanarLayout::PLANE_Y) ? yPlaneSize : uvPlaneSize; for (size_t i = 1; i < 3; ++i) { offsets[planeOrder[i]] = offsets[planeOrder[i - 1]] + planeSize; if (!contiguous) { offsets[planeOrder[i]] += kGapLength; } planeSize = (planeOrder[i] == C2PlanarLayout::PLANE_Y) ? yPlaneSize : uvPlaneSize; if (!planar // semi-planar && planeOrder[i - 1] != C2PlanarLayout::PLANE_Y && planeOrder[i] != C2PlanarLayout::PLANE_Y) { offsets[planeOrder[i]] = offsets[planeOrder[i - 1]] + 1; planeSize = uvPlaneSize * 2 - 1; } } std::shared_ptr<C2GraphicBlock> block = CreateGraphicBlock( kWidth, kHeight, layout, capacity, offsets); FillBlock(block); return C2Buffer::CreateGraphicBuffer( block->share(block->crop(), C2Fence())); } static bool VerifyClientBuffer( const sp<MediaCodecBuffer> &buffer, std::string *errorMsg) { *errorMsg = ""; sp<ABuffer> imageData; if (!buffer->format()->findBuffer("image-data", &imageData)) { *errorMsg = "Missing image data"; return false; } MediaImage2 *mediaImage = (MediaImage2 *)imageData->data(); if (mediaImage->mType != MediaImage2::MEDIA_IMAGE_TYPE_YUV) { *errorMsg = AStringPrintf("Unexpected type: %d", mediaImage->mType).c_str(); return false; } std::string planeErrorMsg; if (!VerifyPlane(mediaImage, buffer->base(), MediaImage2::Y, 'Y', &planeErrorMsg)) { *errorMsg = "Y plane does not match: " + planeErrorMsg; return false; } if (!VerifyPlane(mediaImage, buffer->base(), MediaImage2::U, 'U', &planeErrorMsg)) { *errorMsg = "U plane does not match: " + planeErrorMsg; return false; } if (!VerifyPlane(mediaImage, buffer->base(), MediaImage2::V, 'V', &planeErrorMsg)) { *errorMsg = "V plane does not match: " + planeErrorMsg; return false; } int32_t width, height, stride; buffer->format()->findInt32(KEY_WIDTH, &width); buffer->format()->findInt32(KEY_HEIGHT, &height); buffer->format()->findInt32(KEY_STRIDE, &stride); MediaImage2 legacyYLayout = { MediaImage2::MEDIA_IMAGE_TYPE_Y, 1, // mNumPlanes uint32_t(width), uint32_t(height), 8, 8, {}, // mPlane }; legacyYLayout.mPlane[MediaImage2::Y] = { 0, // mOffset 1, // mColInc stride, // mRowInc 1, // mHorizSubsampling 1, // mVertSubsampling }; if (!VerifyPlane(&legacyYLayout, buffer->data(), MediaImage2::Y, 'Y', &planeErrorMsg)) { *errorMsg = "Y plane by legacy layout does not match: " + planeErrorMsg; return false; } return true; } }; TEST_P(LayoutTest, VerifyLayout) { std::shared_ptr<RawGraphicOutputBuffers> buffers = GetRawGraphicOutputBuffers(kWidth, kHeight); std::shared_ptr<C2Buffer> c2Buffer = CreateAndFillBufferFromParam(GetParam()); ASSERT_NE(nullptr, c2Buffer); sp<MediaCodecBuffer> clientBuffer; size_t index; ASSERT_EQ(OK, buffers->registerBuffer(c2Buffer, &index, &clientBuffer)); ASSERT_NE(nullptr, clientBuffer); std::string errorMsg; ASSERT_TRUE(VerifyClientBuffer(clientBuffer, &errorMsg)) << errorMsg; } INSTANTIATE_TEST_SUITE_P( RawGraphicOutputBuffersTest, LayoutTest, ::testing::Combine( ::testing::Bool(), /* contiguous */ ::testing::Values("YUV", "YVU", "UVY", "VUY"), ::testing::Bool(), /* planar */ ::testing::Values(320, 512)), [](const ::testing::TestParamInfo<LayoutTest::ParamType> &info) { std::string contiguous = std::get<0>(info.param) ? "Contiguous" : "Noncontiguous"; std::string planar = std::get<2>(info.param) ? "Planar" : "SemiPlanar"; return contiguous + std::get<1>(info.param) + planar + std::to_string(std::get<3>(info.param)); }); } // namespace android Loading
media/codec2/sfplugin/CCodecBuffers.cpp +3 −0 Original line number Diff line number Diff line Loading @@ -96,6 +96,9 @@ void CCodecBuffers::handleImageData(const sp<Codec2Buffer> &buffer) { int32_t vstride = int32_t(offsetDelta / stride); newFormat->setInt32(KEY_SLICE_HEIGHT, vstride); ALOGD("[%s] updating vstride = %d", mName, vstride); buffer->setRange( img->mPlane[0].mOffset, buffer->size() - img->mPlane[0].mOffset); } } setFormat(newFormat); Loading
media/codec2/sfplugin/Codec2Buffer.cpp +8 −6 Original line number Diff line number Diff line Loading @@ -276,20 +276,22 @@ public: 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); int64_t planeStride = std::abs(plane.rowInc / plane.colInc); ssize_t minOffset = plane.minOffset( mWidth / plane.colSampling, mHeight / plane.rowSampling); ssize_t maxOffset = plane.maxOffset( mWidth / plane.colSampling, mHeight / plane.rowSampling); 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) * align(mHeight, 64) / plane.rowSampling / plane.colSampling * divUp(mAllocatedDepth, 8u); planeSize += planeStride * divUp(mAllocatedDepth, 8u) * align(mHeight, 64) / plane.rowSampling; } if ((maxPtr - minPtr + 1) <= planeSize) { if (minPtr == mView.data()[0] && (maxPtr - minPtr + 1) <= planeSize) { // FIXME: this is risky as reading/writing data out of bound results // in an undefined behavior, but gralloc does assume a // contiguous mapping Loading
media/codec2/sfplugin/tests/CCodecBuffers_test.cpp +445 −5 Original line number Diff line number Diff line Loading @@ -18,22 +18,31 @@ #include <gtest/gtest.h> #include <media/stagefright/foundation/AString.h> #include <media/stagefright/MediaCodecConstants.h> #include <C2BlockInternal.h> #include <C2PlatformSupport.h> namespace android { static std::shared_ptr<RawGraphicOutputBuffers> GetRawGraphicOutputBuffers( int32_t width, int32_t height) { std::shared_ptr<RawGraphicOutputBuffers> buffers = std::make_shared<RawGraphicOutputBuffers>("test"); sp<AMessage> format{new AMessage}; format->setInt32(KEY_WIDTH, width); format->setInt32(KEY_HEIGHT, height); buffers->setFormat(format); return buffers; } TEST(RawGraphicOutputBuffersTest, ChangeNumSlots) { constexpr int32_t kWidth = 3840; constexpr int32_t kHeight = 2160; std::shared_ptr<RawGraphicOutputBuffers> buffers = std::make_shared<RawGraphicOutputBuffers>("test"); sp<AMessage> format{new AMessage}; format->setInt32("width", kWidth); format->setInt32("height", kHeight); buffers->setFormat(format); GetRawGraphicOutputBuffers(kWidth, kHeight); std::shared_ptr<C2BlockPool> pool; ASSERT_EQ(OK, GetCodec2BlockPool(C2BlockPool::BASIC_GRAPHIC, nullptr, &pool)); Loading Loading @@ -96,4 +105,435 @@ TEST(RawGraphicOutputBuffersTest, ChangeNumSlots) { } } class TestGraphicAllocation : public C2GraphicAllocation { public: TestGraphicAllocation( uint32_t width, uint32_t height, const C2PlanarLayout &layout, size_t capacity, std::vector<size_t> offsets) : C2GraphicAllocation(width, height), mLayout(layout), mMemory(capacity, 0xAA), mOffsets(offsets) { } c2_status_t map( C2Rect rect, C2MemoryUsage usage, C2Fence *fence, C2PlanarLayout *layout, uint8_t **addr) override { (void)rect; (void)usage; (void)fence; *layout = mLayout; for (size_t i = 0; i < mLayout.numPlanes; ++i) { addr[i] = mMemory.data() + mOffsets[i]; } return C2_OK; } c2_status_t unmap(uint8_t **, C2Rect, C2Fence *) override { return C2_OK; } C2Allocator::id_t getAllocatorId() const override { return -1; } const C2Handle *handle() const override { return nullptr; } bool equals(const std::shared_ptr<const C2GraphicAllocation> &other) const override { return other.get() == this; } private: C2PlanarLayout mLayout; std::vector<uint8_t> mMemory; std::vector<uint8_t *> mAddr; std::vector<size_t> mOffsets; }; class LayoutTest : public ::testing::TestWithParam<std::tuple<bool, std::string, bool, int32_t>> { private: static C2PlanarLayout YUVPlanarLayout(int32_t stride) { C2PlanarLayout layout = { C2PlanarLayout::TYPE_YUV, 3, /* numPlanes */ 3, /* rootPlanes */ {}, /* planes --- to be filled below */ }; layout.planes[C2PlanarLayout::PLANE_Y] = { C2PlaneInfo::CHANNEL_Y, 1, /* colInc */ stride, /* rowInc */ 1, /* colSampling */ 1, /* rowSampling */ 8, /* allocatedDepth */ 8, /* bitDepth */ 0, /* rightShift */ C2PlaneInfo::NATIVE, C2PlanarLayout::PLANE_Y, /* rootIx */ 0, /* offset */ }; layout.planes[C2PlanarLayout::PLANE_U] = { C2PlaneInfo::CHANNEL_CB, 1, /* colInc */ stride / 2, /* rowInc */ 2, /* colSampling */ 2, /* rowSampling */ 8, /* allocatedDepth */ 8, /* bitDepth */ 0, /* rightShift */ C2PlaneInfo::NATIVE, C2PlanarLayout::PLANE_U, /* rootIx */ 0, /* offset */ }; layout.planes[C2PlanarLayout::PLANE_V] = { C2PlaneInfo::CHANNEL_CR, 1, /* colInc */ stride / 2, /* rowInc */ 2, /* colSampling */ 2, /* rowSampling */ 8, /* allocatedDepth */ 8, /* bitDepth */ 0, /* rightShift */ C2PlaneInfo::NATIVE, C2PlanarLayout::PLANE_V, /* rootIx */ 0, /* offset */ }; return layout; } static C2PlanarLayout YUVSemiPlanarLayout(int32_t stride) { C2PlanarLayout layout = { C2PlanarLayout::TYPE_YUV, 3, /* numPlanes */ 2, /* rootPlanes */ {}, /* planes --- to be filled below */ }; layout.planes[C2PlanarLayout::PLANE_Y] = { C2PlaneInfo::CHANNEL_Y, 1, /* colInc */ stride, /* rowInc */ 1, /* colSampling */ 1, /* rowSampling */ 8, /* allocatedDepth */ 8, /* bitDepth */ 0, /* rightShift */ C2PlaneInfo::NATIVE, C2PlanarLayout::PLANE_Y, /* rootIx */ 0, /* offset */ }; layout.planes[C2PlanarLayout::PLANE_U] = { C2PlaneInfo::CHANNEL_CB, 2, /* colInc */ stride, /* rowInc */ 2, /* colSampling */ 2, /* rowSampling */ 8, /* allocatedDepth */ 8, /* bitDepth */ 0, /* rightShift */ C2PlaneInfo::NATIVE, C2PlanarLayout::PLANE_U, /* rootIx */ 0, /* offset */ }; layout.planes[C2PlanarLayout::PLANE_V] = { C2PlaneInfo::CHANNEL_CR, 2, /* colInc */ stride, /* rowInc */ 2, /* colSampling */ 2, /* rowSampling */ 8, /* allocatedDepth */ 8, /* bitDepth */ 0, /* rightShift */ C2PlaneInfo::NATIVE, C2PlanarLayout::PLANE_U, /* rootIx */ 1, /* offset */ }; return layout; } static C2PlanarLayout YVUSemiPlanarLayout(int32_t stride) { C2PlanarLayout layout = { C2PlanarLayout::TYPE_YUV, 3, /* numPlanes */ 2, /* rootPlanes */ {}, /* planes --- to be filled below */ }; layout.planes[C2PlanarLayout::PLANE_Y] = { C2PlaneInfo::CHANNEL_Y, 1, /* colInc */ stride, /* rowInc */ 1, /* colSampling */ 1, /* rowSampling */ 8, /* allocatedDepth */ 8, /* bitDepth */ 0, /* rightShift */ C2PlaneInfo::NATIVE, C2PlanarLayout::PLANE_Y, /* rootIx */ 0, /* offset */ }; layout.planes[C2PlanarLayout::PLANE_U] = { C2PlaneInfo::CHANNEL_CB, 2, /* colInc */ stride, /* rowInc */ 2, /* colSampling */ 2, /* rowSampling */ 8, /* allocatedDepth */ 8, /* bitDepth */ 0, /* rightShift */ C2PlaneInfo::NATIVE, C2PlanarLayout::PLANE_V, /* rootIx */ 1, /* offset */ }; layout.planes[C2PlanarLayout::PLANE_V] = { C2PlaneInfo::CHANNEL_CR, 2, /* colInc */ stride, /* rowInc */ 2, /* colSampling */ 2, /* rowSampling */ 8, /* allocatedDepth */ 8, /* bitDepth */ 0, /* rightShift */ C2PlaneInfo::NATIVE, C2PlanarLayout::PLANE_V, /* rootIx */ 0, /* offset */ }; return layout; } static std::shared_ptr<C2GraphicBlock> CreateGraphicBlock( uint32_t width, uint32_t height, const C2PlanarLayout &layout, size_t capacity, std::vector<size_t> offsets) { std::shared_ptr<C2GraphicAllocation> alloc = std::make_shared<TestGraphicAllocation>( width, height, layout, capacity, offsets); return _C2BlockFactory::CreateGraphicBlock(alloc); } static constexpr uint8_t GetPixelValue(uint8_t value, uint32_t row, uint32_t col) { return (uint32_t(value) * row + col) & 0xFF; } static void FillPlane(C2GraphicView &view, size_t index, uint8_t value) { C2PlanarLayout layout = view.layout(); uint8_t *rowPtr = view.data()[index]; C2PlaneInfo plane = layout.planes[index]; for (uint32_t row = 0; row < view.height() / plane.rowSampling; ++row) { uint8_t *colPtr = rowPtr; for (uint32_t col = 0; col < view.width() / plane.colSampling; ++col) { *colPtr = GetPixelValue(value, row, col); colPtr += plane.colInc; } rowPtr += plane.rowInc; } } static void FillBlock(const std::shared_ptr<C2GraphicBlock> &block) { C2GraphicView view = block->map().get(); FillPlane(view, C2PlanarLayout::PLANE_Y, 'Y'); FillPlane(view, C2PlanarLayout::PLANE_U, 'U'); FillPlane(view, C2PlanarLayout::PLANE_V, 'V'); } static bool VerifyPlane( const MediaImage2 *mediaImage, const uint8_t *base, uint32_t index, uint8_t value, std::string *errorMsg) { *errorMsg = ""; MediaImage2::PlaneInfo plane = mediaImage->mPlane[index]; const uint8_t *rowPtr = base + plane.mOffset; for (uint32_t row = 0; row < mediaImage->mHeight / plane.mVertSubsampling; ++row) { const uint8_t *colPtr = rowPtr; for (uint32_t col = 0; col < mediaImage->mWidth / plane.mHorizSubsampling; ++col) { if (GetPixelValue(value, row, col) != *colPtr) { *errorMsg = AStringPrintf("row=%u col=%u expected=%02x actual=%02x", row, col, GetPixelValue(value, row, col), *colPtr).c_str(); return false; } colPtr += plane.mColInc; } rowPtr += plane.mRowInc; } return true; } public: static constexpr int32_t kWidth = 320; static constexpr int32_t kHeight = 240; static constexpr int32_t kGapLength = kWidth * kHeight * 10; static std::shared_ptr<C2Buffer> CreateAndFillBufferFromParam(const ParamType ¶m) { bool contiguous = std::get<0>(param); std::string planeOrderStr = std::get<1>(param); bool planar = std::get<2>(param); int32_t stride = std::get<3>(param); C2PlanarLayout::plane_index_t planeOrder[3]; C2PlanarLayout layout; if (planeOrderStr.size() != 3) { return nullptr; } for (size_t i = 0; i < 3; ++i) { C2PlanarLayout::plane_index_t planeIndex; switch (planeOrderStr[i]) { case 'Y': planeIndex = C2PlanarLayout::PLANE_Y; break; case 'U': planeIndex = C2PlanarLayout::PLANE_U; break; case 'V': planeIndex = C2PlanarLayout::PLANE_V; break; default: return nullptr; } planeOrder[i] = planeIndex; } if (planar) { layout = YUVPlanarLayout(stride); } else { // semi-planar for (size_t i = 0; i < 3; ++i) { if (planeOrder[i] == C2PlanarLayout::PLANE_U) { layout = YUVSemiPlanarLayout(stride); break; } if (planeOrder[i] == C2PlanarLayout::PLANE_V) { layout = YVUSemiPlanarLayout(stride); break; } } } size_t yPlaneSize = stride * kHeight; size_t uvPlaneSize = stride * kHeight / 4; size_t capacity = yPlaneSize + uvPlaneSize * 2; std::vector<size_t> offsets(3); if (!contiguous) { if (planar) { capacity += kGapLength * 2; } else { // semi-planar capacity += kGapLength; } } offsets[planeOrder[0]] = 0; size_t planeSize = (planeOrder[0] == C2PlanarLayout::PLANE_Y) ? yPlaneSize : uvPlaneSize; for (size_t i = 1; i < 3; ++i) { offsets[planeOrder[i]] = offsets[planeOrder[i - 1]] + planeSize; if (!contiguous) { offsets[planeOrder[i]] += kGapLength; } planeSize = (planeOrder[i] == C2PlanarLayout::PLANE_Y) ? yPlaneSize : uvPlaneSize; if (!planar // semi-planar && planeOrder[i - 1] != C2PlanarLayout::PLANE_Y && planeOrder[i] != C2PlanarLayout::PLANE_Y) { offsets[planeOrder[i]] = offsets[planeOrder[i - 1]] + 1; planeSize = uvPlaneSize * 2 - 1; } } std::shared_ptr<C2GraphicBlock> block = CreateGraphicBlock( kWidth, kHeight, layout, capacity, offsets); FillBlock(block); return C2Buffer::CreateGraphicBuffer( block->share(block->crop(), C2Fence())); } static bool VerifyClientBuffer( const sp<MediaCodecBuffer> &buffer, std::string *errorMsg) { *errorMsg = ""; sp<ABuffer> imageData; if (!buffer->format()->findBuffer("image-data", &imageData)) { *errorMsg = "Missing image data"; return false; } MediaImage2 *mediaImage = (MediaImage2 *)imageData->data(); if (mediaImage->mType != MediaImage2::MEDIA_IMAGE_TYPE_YUV) { *errorMsg = AStringPrintf("Unexpected type: %d", mediaImage->mType).c_str(); return false; } std::string planeErrorMsg; if (!VerifyPlane(mediaImage, buffer->base(), MediaImage2::Y, 'Y', &planeErrorMsg)) { *errorMsg = "Y plane does not match: " + planeErrorMsg; return false; } if (!VerifyPlane(mediaImage, buffer->base(), MediaImage2::U, 'U', &planeErrorMsg)) { *errorMsg = "U plane does not match: " + planeErrorMsg; return false; } if (!VerifyPlane(mediaImage, buffer->base(), MediaImage2::V, 'V', &planeErrorMsg)) { *errorMsg = "V plane does not match: " + planeErrorMsg; return false; } int32_t width, height, stride; buffer->format()->findInt32(KEY_WIDTH, &width); buffer->format()->findInt32(KEY_HEIGHT, &height); buffer->format()->findInt32(KEY_STRIDE, &stride); MediaImage2 legacyYLayout = { MediaImage2::MEDIA_IMAGE_TYPE_Y, 1, // mNumPlanes uint32_t(width), uint32_t(height), 8, 8, {}, // mPlane }; legacyYLayout.mPlane[MediaImage2::Y] = { 0, // mOffset 1, // mColInc stride, // mRowInc 1, // mHorizSubsampling 1, // mVertSubsampling }; if (!VerifyPlane(&legacyYLayout, buffer->data(), MediaImage2::Y, 'Y', &planeErrorMsg)) { *errorMsg = "Y plane by legacy layout does not match: " + planeErrorMsg; return false; } return true; } }; TEST_P(LayoutTest, VerifyLayout) { std::shared_ptr<RawGraphicOutputBuffers> buffers = GetRawGraphicOutputBuffers(kWidth, kHeight); std::shared_ptr<C2Buffer> c2Buffer = CreateAndFillBufferFromParam(GetParam()); ASSERT_NE(nullptr, c2Buffer); sp<MediaCodecBuffer> clientBuffer; size_t index; ASSERT_EQ(OK, buffers->registerBuffer(c2Buffer, &index, &clientBuffer)); ASSERT_NE(nullptr, clientBuffer); std::string errorMsg; ASSERT_TRUE(VerifyClientBuffer(clientBuffer, &errorMsg)) << errorMsg; } INSTANTIATE_TEST_SUITE_P( RawGraphicOutputBuffersTest, LayoutTest, ::testing::Combine( ::testing::Bool(), /* contiguous */ ::testing::Values("YUV", "YVU", "UVY", "VUY"), ::testing::Bool(), /* planar */ ::testing::Values(320, 512)), [](const ::testing::TestParamInfo<LayoutTest::ParamType> &info) { std::string contiguous = std::get<0>(info.param) ? "Contiguous" : "Noncontiguous"; std::string planar = std::get<2>(info.param) ? "Planar" : "SemiPlanar"; return contiguous + std::get<1>(info.param) + planar + std::to_string(std::get<3>(info.param)); }); } // namespace android