From 607c1d606efd9f955ad7545dbfdaf38450c5ce20 Mon Sep 17 00:00:00 2001 From: Wonsik Kim Date: Thu, 3 Dec 2020 11:07:58 -0800 Subject: [PATCH 29/30] CCodec: fix ByteBuffer mode image - Set offset to (0,0) of Y plane. - Fix wrapping criteria. Bug: 169379476 Test: atest CtsMediaTestCases -- --module-arg CtsMediaTestCases:size:small Test: atest ccodec_unit_test Change-Id: Ic7e074ddaef277833707363b0b9fecfe210bd57f (cherry picked from commit 2eb063152f67e886472ed42c07f3634a5eb63f19) --- media/codec2/sfplugin/CCodecBuffers.cpp | 3 + media/codec2/sfplugin/Codec2Buffer.cpp | 14 +- .../sfplugin/tests/CCodecBuffers_test.cpp | 450 +++++++++++++++++- 3 files changed, 456 insertions(+), 11 deletions(-) diff --git a/media/codec2/sfplugin/CCodecBuffers.cpp b/media/codec2/sfplugin/CCodecBuffers.cpp index 692da584ce..566a18fbee 100644 --- a/media/codec2/sfplugin/CCodecBuffers.cpp +++ b/media/codec2/sfplugin/CCodecBuffers.cpp @@ -96,6 +96,9 @@ void CCodecBuffers::handleImageData(const sp &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); diff --git a/media/codec2/sfplugin/Codec2Buffer.cpp b/media/codec2/sfplugin/Codec2Buffer.cpp index 25e7da9206..19414a0a0c 100644 --- a/media/codec2/sfplugin/Codec2Buffer.cpp +++ b/media/codec2/sfplugin/Codec2Buffer.cpp @@ -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 diff --git a/media/codec2/sfplugin/tests/CCodecBuffers_test.cpp b/media/codec2/sfplugin/tests/CCodecBuffers_test.cpp index 5bee605276..ad8f6e555b 100644 --- a/media/codec2/sfplugin/tests/CCodecBuffers_test.cpp +++ b/media/codec2/sfplugin/tests/CCodecBuffers_test.cpp @@ -18,22 +18,31 @@ #include +#include #include +#include #include namespace android { +static std::shared_ptr GetRawGraphicOutputBuffers( + int32_t width, int32_t height) { + std::shared_ptr buffers = + std::make_shared("test"); + sp 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 buffers = - std::make_shared("test"); - sp format{new AMessage}; - format->setInt32("width", kWidth); - format->setInt32("height", kHeight); - buffers->setFormat(format); + GetRawGraphicOutputBuffers(kWidth, kHeight); std::shared_ptr pool; ASSERT_EQ(OK, GetCodec2BlockPool(C2BlockPool::BASIC_GRAPHIC, nullptr, &pool)); @@ -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 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 &other) const override { + return other.get() == this; + } + +private: + C2PlanarLayout mLayout; + std::vector mMemory; + std::vector mAddr; + std::vector mOffsets; +}; + +class LayoutTest : public ::testing::TestWithParam> { +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 CreateGraphicBlock( + uint32_t width, + uint32_t height, + const C2PlanarLayout &layout, + size_t capacity, + std::vector offsets) { + std::shared_ptr alloc = std::make_shared( + 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 &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 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 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 block = CreateGraphicBlock( + kWidth, + kHeight, + layout, + capacity, + offsets); + FillBlock(block); + return C2Buffer::CreateGraphicBuffer( + block->share(block->crop(), C2Fence())); + } + + static bool VerifyClientBuffer( + const sp &buffer, std::string *errorMsg) { + *errorMsg = ""; + sp 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 buffers = + GetRawGraphicOutputBuffers(kWidth, kHeight); + + std::shared_ptr c2Buffer = CreateAndFillBufferFromParam(GetParam()); + ASSERT_NE(nullptr, c2Buffer); + sp 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 &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 -- 2.25.1