918 lines
34 KiB
C++
918 lines
34 KiB
C++
/*
|
|
* Copyright 2020 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.
|
|
*/
|
|
|
|
#include "CCodecBuffers.h"
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <codec2/hidl/client.h>
|
|
#include <media/stagefright/MediaCodecConstants.h>
|
|
|
|
#include <C2BlockInternal.h>
|
|
#include <C2PlatformSupport.h>
|
|
#include <Codec2Mapper.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 =
|
|
GetRawGraphicOutputBuffers(kWidth, kHeight);
|
|
|
|
std::shared_ptr<C2BlockPool> pool;
|
|
ASSERT_EQ(OK, GetCodec2BlockPool(C2BlockPool::BASIC_GRAPHIC, nullptr, &pool));
|
|
|
|
// Register 4 buffers
|
|
std::vector<sp<MediaCodecBuffer>> clientBuffers;
|
|
auto registerBuffer = [&buffers, &clientBuffers, &pool] {
|
|
std::shared_ptr<C2GraphicBlock> block;
|
|
ASSERT_EQ(OK, pool->fetchGraphicBlock(
|
|
kWidth, kHeight, HAL_PIXEL_FORMAT_YCbCr_420_888,
|
|
C2MemoryUsage{C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}, &block));
|
|
std::shared_ptr<C2Buffer> c2Buffer = C2Buffer::CreateGraphicBuffer(block->share(
|
|
block->crop(), C2Fence{}));
|
|
size_t index;
|
|
sp<MediaCodecBuffer> clientBuffer;
|
|
ASSERT_EQ(OK, buffers->registerBuffer(c2Buffer, &index, &clientBuffer));
|
|
ASSERT_NE(nullptr, clientBuffer);
|
|
while (clientBuffers.size() <= index) {
|
|
clientBuffers.emplace_back();
|
|
}
|
|
ASSERT_EQ(nullptr, clientBuffers[index]) << "index = " << index;
|
|
clientBuffers[index] = clientBuffer;
|
|
};
|
|
for (int i = 0; i < 4; ++i) {
|
|
registerBuffer();
|
|
}
|
|
|
|
// Release 2 buffers
|
|
auto releaseBuffer = [&buffers, &clientBuffers, kWidth, kHeight](int index) {
|
|
std::shared_ptr<C2Buffer> c2Buffer;
|
|
ASSERT_TRUE(buffers->releaseBuffer(clientBuffers[index], &c2Buffer))
|
|
<< "index = " << index;
|
|
clientBuffers[index] = nullptr;
|
|
// Sanity checks
|
|
ASSERT_TRUE(c2Buffer->data().linearBlocks().empty());
|
|
ASSERT_EQ(1u, c2Buffer->data().graphicBlocks().size());
|
|
C2ConstGraphicBlock block = c2Buffer->data().graphicBlocks().front();
|
|
ASSERT_EQ(kWidth, block.width());
|
|
ASSERT_EQ(kHeight, block.height());
|
|
};
|
|
for (int i = 0, index = 0; i < 2 && index < clientBuffers.size(); ++index) {
|
|
if (clientBuffers[index] == nullptr) {
|
|
continue;
|
|
}
|
|
releaseBuffer(index);
|
|
++i;
|
|
}
|
|
|
|
// Simulate # of slots 4->16
|
|
for (int i = 2; i < 16; ++i) {
|
|
registerBuffer();
|
|
}
|
|
|
|
// Release everything
|
|
for (int index = 0; index < clientBuffers.size(); ++index) {
|
|
if (clientBuffers[index] == nullptr) {
|
|
continue;
|
|
}
|
|
releaseBuffer(index);
|
|
}
|
|
}
|
|
|
|
TEST(RawGraphicOutputBuffersTest, WrapNullBuffer) {
|
|
constexpr int32_t kWidth = 320;
|
|
constexpr int32_t kHeight = 240;
|
|
|
|
std::shared_ptr<RawGraphicOutputBuffers> buffers =
|
|
GetRawGraphicOutputBuffers(kWidth, kHeight);
|
|
|
|
sp<Codec2Buffer> buffer = buffers->wrap(nullptr);
|
|
ASSERT_EQ(nullptr, buffer->base());
|
|
ASSERT_EQ(0, buffer->size());
|
|
ASSERT_EQ(0, buffer->offset());
|
|
}
|
|
|
|
TEST(RawGraphicOutputBuffersTest, FlexYuvColorFormat) {
|
|
constexpr int32_t kWidth = 320;
|
|
constexpr int32_t kHeight = 240;
|
|
|
|
std::vector<uint32_t> flexPixelFormats({HAL_PIXEL_FORMAT_YCbCr_420_888});
|
|
std::shared_ptr<Codec2Client> client = Codec2Client::CreateFromService("default");
|
|
if (client) {
|
|
// Query vendor format for Flexible YUV
|
|
std::vector<std::unique_ptr<C2Param>> heapParams;
|
|
C2StoreFlexiblePixelFormatDescriptorsInfo *pixelFormatInfo = nullptr;
|
|
if (client->query(
|
|
{},
|
|
{C2StoreFlexiblePixelFormatDescriptorsInfo::PARAM_TYPE},
|
|
C2_MAY_BLOCK,
|
|
&heapParams) == C2_OK
|
|
&& heapParams.size() == 1u) {
|
|
pixelFormatInfo = C2StoreFlexiblePixelFormatDescriptorsInfo::From(
|
|
heapParams[0].get());
|
|
} else {
|
|
pixelFormatInfo = nullptr;
|
|
}
|
|
if (pixelFormatInfo && *pixelFormatInfo) {
|
|
for (size_t i = 0; i < pixelFormatInfo->flexCount(); ++i) {
|
|
const C2FlexiblePixelFormatDescriptorStruct &desc =
|
|
pixelFormatInfo->m.values[i];
|
|
if (desc.bitDepth != 8
|
|
|| desc.subsampling != C2Color::YUV_420
|
|
// TODO(b/180076105): some devices report wrong layouts
|
|
// || desc.layout == C2Color::INTERLEAVED_PACKED
|
|
// || desc.layout == C2Color::INTERLEAVED_ALIGNED
|
|
|| desc.layout == C2Color::UNKNOWN_LAYOUT) {
|
|
continue;
|
|
}
|
|
flexPixelFormats.push_back(desc.pixelFormat);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (uint32_t pixelFormat : flexPixelFormats) {
|
|
std::shared_ptr<RawGraphicOutputBuffers> buffers =
|
|
std::make_shared<RawGraphicOutputBuffers>(
|
|
AStringPrintf("test pixel format 0x%x", pixelFormat).c_str());
|
|
|
|
sp<AMessage> format{new AMessage};
|
|
format->setInt32(KEY_WIDTH, kWidth);
|
|
format->setInt32(KEY_HEIGHT, kHeight);
|
|
format->setInt32(KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
|
|
int32_t fwkPixelFormat = 0;
|
|
if (C2Mapper::mapPixelFormatCodecToFramework(pixelFormat, &fwkPixelFormat)) {
|
|
format->setInt32("android._color-format", fwkPixelFormat);
|
|
}
|
|
buffers->setFormat(format);
|
|
|
|
std::shared_ptr<C2BlockPool> pool;
|
|
ASSERT_EQ(OK, GetCodec2BlockPool(C2BlockPool::BASIC_GRAPHIC, nullptr, &pool));
|
|
|
|
std::shared_ptr<C2GraphicBlock> block;
|
|
ASSERT_EQ(OK, pool->fetchGraphicBlock(
|
|
kWidth, kHeight, pixelFormat,
|
|
C2MemoryUsage{C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}, &block));
|
|
|
|
{
|
|
C2GraphicView view = block->map().get();
|
|
C2PlanarLayout layout = view.layout();
|
|
|
|
// Verify the block is in YUV420 format
|
|
ASSERT_EQ(C2PlanarLayout::TYPE_YUV, layout.type);
|
|
ASSERT_EQ(3u, layout.numPlanes);
|
|
const C2PlaneInfo& yPlane = layout.planes[C2PlanarLayout::PLANE_Y];
|
|
const C2PlaneInfo& uPlane = layout.planes[C2PlanarLayout::PLANE_U];
|
|
const C2PlaneInfo& vPlane = layout.planes[C2PlanarLayout::PLANE_V];
|
|
|
|
// Y plane
|
|
ASSERT_EQ(1u, yPlane.colSampling);
|
|
ASSERT_EQ(1u, yPlane.rowSampling);
|
|
ASSERT_EQ(8u, yPlane.allocatedDepth);
|
|
ASSERT_EQ(8u, yPlane.bitDepth);
|
|
ASSERT_EQ(0u, yPlane.rightShift);
|
|
|
|
// U plane
|
|
ASSERT_EQ(2u, uPlane.colSampling);
|
|
ASSERT_EQ(2u, uPlane.rowSampling);
|
|
ASSERT_EQ(8u, uPlane.allocatedDepth);
|
|
ASSERT_EQ(8u, uPlane.bitDepth);
|
|
ASSERT_EQ(0u, uPlane.rightShift);
|
|
|
|
// V plane
|
|
ASSERT_EQ(2u, vPlane.colSampling);
|
|
ASSERT_EQ(2u, vPlane.rowSampling);
|
|
ASSERT_EQ(8u, vPlane.allocatedDepth);
|
|
ASSERT_EQ(8u, vPlane.bitDepth);
|
|
ASSERT_EQ(0u, vPlane.rightShift);
|
|
|
|
uint8_t *yRowPtr = view.data()[C2PlanarLayout::PLANE_Y];
|
|
uint8_t *uRowPtr = view.data()[C2PlanarLayout::PLANE_U];
|
|
uint8_t *vRowPtr = view.data()[C2PlanarLayout::PLANE_V];
|
|
for (int32_t row = 0; row < kHeight; ++row) {
|
|
uint8_t *yPtr = yRowPtr;
|
|
uint8_t *uPtr = uRowPtr;
|
|
uint8_t *vPtr = vRowPtr;
|
|
for (int32_t col = 0; col < kWidth; ++col) {
|
|
*yPtr = ((row + col) & 0xFF);
|
|
yPtr += yPlane.colInc;
|
|
|
|
if (row < kHeight / 2 && col < kWidth / 2) {
|
|
*uPtr = ((row + col + 1) & 0xFF);
|
|
*vPtr = ((row + col + 2) & 0xFF);
|
|
uPtr += uPlane.colInc;
|
|
vPtr += vPlane.colInc;
|
|
}
|
|
}
|
|
yRowPtr += yPlane.rowInc;
|
|
if (row < kHeight / 2) {
|
|
uRowPtr += uPlane.rowInc;
|
|
vRowPtr += vPlane.rowInc;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<C2Buffer> c2Buffer = C2Buffer::CreateGraphicBuffer(block->share(
|
|
block->crop(), C2Fence{}));
|
|
size_t index;
|
|
sp<MediaCodecBuffer> clientBuffer;
|
|
ASSERT_EQ(OK, buffers->registerBuffer(c2Buffer, &index, &clientBuffer));
|
|
ASSERT_NE(nullptr, clientBuffer);
|
|
sp<ABuffer> imageData;
|
|
ASSERT_TRUE(clientBuffer->format()->findBuffer("image-data", &imageData));
|
|
MediaImage2 *img = (MediaImage2 *)imageData->data();
|
|
ASSERT_EQ(MediaImage2::MEDIA_IMAGE_TYPE_YUV, img->mType);
|
|
ASSERT_EQ(3u, img->mNumPlanes);
|
|
ASSERT_EQ(kWidth, img->mWidth);
|
|
ASSERT_EQ(kHeight, img->mHeight);
|
|
ASSERT_EQ(8u, img->mBitDepth);
|
|
ASSERT_EQ(8u, img->mBitDepthAllocated);
|
|
const MediaImage2::PlaneInfo &yPlane = img->mPlane[MediaImage2::Y];
|
|
const MediaImage2::PlaneInfo &uPlane = img->mPlane[MediaImage2::U];
|
|
const MediaImage2::PlaneInfo &vPlane = img->mPlane[MediaImage2::V];
|
|
ASSERT_EQ(1u, yPlane.mHorizSubsampling);
|
|
ASSERT_EQ(1u, yPlane.mVertSubsampling);
|
|
ASSERT_EQ(2u, uPlane.mHorizSubsampling);
|
|
ASSERT_EQ(2u, uPlane.mVertSubsampling);
|
|
ASSERT_EQ(2u, vPlane.mHorizSubsampling);
|
|
ASSERT_EQ(2u, vPlane.mVertSubsampling);
|
|
|
|
uint8_t *yRowPtr = clientBuffer->data() + yPlane.mOffset;
|
|
uint8_t *uRowPtr = clientBuffer->data() + uPlane.mOffset;
|
|
uint8_t *vRowPtr = clientBuffer->data() + vPlane.mOffset;
|
|
for (int32_t row = 0; row < kHeight; ++row) {
|
|
uint8_t *yPtr = yRowPtr;
|
|
uint8_t *uPtr = uRowPtr;
|
|
uint8_t *vPtr = vRowPtr;
|
|
for (int32_t col = 0; col < kWidth; ++col) {
|
|
ASSERT_EQ((row + col) & 0xFF, *yPtr);
|
|
yPtr += yPlane.mColInc;
|
|
if (row < kHeight / 2 && col < kWidth / 2) {
|
|
ASSERT_EQ((row + col + 1) & 0xFF, *uPtr);
|
|
ASSERT_EQ((row + col + 2) & 0xFF, *vPtr);
|
|
uPtr += uPlane.mColInc;
|
|
vPtr += vPlane.mColInc;
|
|
}
|
|
}
|
|
yRowPtr += yPlane.mRowInc;
|
|
if (row < kHeight / 2) {
|
|
uRowPtr += uPlane.mRowInc;
|
|
vRowPtr += vPlane.mRowInc;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(RawGraphicOutputBuffersTest, P010ColorFormat) {
|
|
constexpr int32_t kWidth = 320;
|
|
constexpr int32_t kHeight = 240;
|
|
|
|
std::shared_ptr<RawGraphicOutputBuffers> buffers =
|
|
std::make_shared<RawGraphicOutputBuffers>("test P010");
|
|
|
|
sp<AMessage> format{new AMessage};
|
|
format->setInt32(KEY_WIDTH, kWidth);
|
|
format->setInt32(KEY_HEIGHT, kHeight);
|
|
format->setInt32(KEY_COLOR_FORMAT, COLOR_FormatYUVP010);
|
|
int32_t fwkPixelFormat = 0;
|
|
if (C2Mapper::mapPixelFormatCodecToFramework(HAL_PIXEL_FORMAT_YCBCR_P010, &fwkPixelFormat)) {
|
|
format->setInt32("android._color-format", fwkPixelFormat);
|
|
}
|
|
buffers->setFormat(format);
|
|
|
|
std::shared_ptr<C2BlockPool> pool;
|
|
ASSERT_EQ(OK, GetCodec2BlockPool(C2BlockPool::BASIC_GRAPHIC, nullptr, &pool));
|
|
|
|
std::shared_ptr<C2GraphicBlock> block;
|
|
c2_status_t err = pool->fetchGraphicBlock(
|
|
kWidth, kHeight, HAL_PIXEL_FORMAT_YCBCR_P010,
|
|
C2MemoryUsage{C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}, &block);
|
|
if (err != C2_OK) {
|
|
GTEST_SKIP();
|
|
}
|
|
|
|
{
|
|
C2GraphicView view = block->map().get();
|
|
C2PlanarLayout layout = view.layout();
|
|
|
|
// Verify the block is in YUV420 format
|
|
ASSERT_EQ(C2PlanarLayout::TYPE_YUV, layout.type);
|
|
ASSERT_EQ(3u, layout.numPlanes);
|
|
const C2PlaneInfo& yPlane = layout.planes[C2PlanarLayout::PLANE_Y];
|
|
const C2PlaneInfo& uPlane = layout.planes[C2PlanarLayout::PLANE_U];
|
|
const C2PlaneInfo& vPlane = layout.planes[C2PlanarLayout::PLANE_V];
|
|
|
|
// Y plane
|
|
ASSERT_EQ(1u, yPlane.colSampling);
|
|
ASSERT_EQ(1u, yPlane.rowSampling);
|
|
ASSERT_EQ(16u, yPlane.allocatedDepth);
|
|
ASSERT_EQ(10u, yPlane.bitDepth);
|
|
ASSERT_EQ(6u, yPlane.rightShift);
|
|
|
|
// U plane
|
|
ASSERT_EQ(2u, uPlane.colSampling);
|
|
ASSERT_EQ(2u, uPlane.rowSampling);
|
|
ASSERT_EQ(16u, uPlane.allocatedDepth);
|
|
ASSERT_EQ(10u, uPlane.bitDepth);
|
|
ASSERT_EQ(6u, uPlane.rightShift);
|
|
|
|
// V plane
|
|
ASSERT_EQ(2u, vPlane.colSampling);
|
|
ASSERT_EQ(2u, vPlane.rowSampling);
|
|
ASSERT_EQ(16u, vPlane.allocatedDepth);
|
|
ASSERT_EQ(10u, vPlane.bitDepth);
|
|
ASSERT_EQ(6u, vPlane.rightShift);
|
|
|
|
uint8_t *yRowPtr = view.data()[C2PlanarLayout::PLANE_Y];
|
|
uint8_t *uRowPtr = view.data()[C2PlanarLayout::PLANE_U];
|
|
uint8_t *vRowPtr = view.data()[C2PlanarLayout::PLANE_V];
|
|
for (int32_t row = 0; row < kHeight; ++row) {
|
|
uint8_t *yPtr = yRowPtr;
|
|
uint8_t *uPtr = uRowPtr;
|
|
uint8_t *vPtr = vRowPtr;
|
|
for (int32_t col = 0; col < kWidth; ++col) {
|
|
yPtr[0] = ((row + col) & 0x3) << 6;
|
|
yPtr[1] = ((row + col) & 0x3FC) >> 2;
|
|
yPtr += yPlane.colInc;
|
|
|
|
if (row < kHeight / 2 && col < kWidth / 2) {
|
|
uPtr[0] = ((row + col + 1) & 0x3) << 6;
|
|
uPtr[1] = ((row + col + 1) & 0x3FC) >> 2;
|
|
vPtr[0] = ((row + col + 2) & 0x3) << 6;
|
|
vPtr[1] = ((row + col + 2) & 0x3FC) >> 2;
|
|
uPtr += uPlane.colInc;
|
|
vPtr += vPlane.colInc;
|
|
}
|
|
}
|
|
yRowPtr += yPlane.rowInc;
|
|
if (row < kHeight / 2) {
|
|
uRowPtr += uPlane.rowInc;
|
|
vRowPtr += vPlane.rowInc;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<C2Buffer> c2Buffer = C2Buffer::CreateGraphicBuffer(block->share(
|
|
block->crop(), C2Fence{}));
|
|
size_t index;
|
|
sp<MediaCodecBuffer> clientBuffer;
|
|
ASSERT_EQ(OK, buffers->registerBuffer(c2Buffer, &index, &clientBuffer));
|
|
ASSERT_NE(nullptr, clientBuffer);
|
|
sp<ABuffer> imageData;
|
|
ASSERT_TRUE(clientBuffer->format()->findBuffer("image-data", &imageData));
|
|
MediaImage2 *img = (MediaImage2 *)imageData->data();
|
|
ASSERT_EQ(MediaImage2::MEDIA_IMAGE_TYPE_YUV, img->mType);
|
|
ASSERT_EQ(3u, img->mNumPlanes);
|
|
ASSERT_EQ(kWidth, img->mWidth);
|
|
ASSERT_EQ(kHeight, img->mHeight);
|
|
ASSERT_EQ(10u, img->mBitDepth);
|
|
ASSERT_EQ(16u, img->mBitDepthAllocated);
|
|
const MediaImage2::PlaneInfo &yPlane = img->mPlane[MediaImage2::Y];
|
|
const MediaImage2::PlaneInfo &uPlane = img->mPlane[MediaImage2::U];
|
|
const MediaImage2::PlaneInfo &vPlane = img->mPlane[MediaImage2::V];
|
|
ASSERT_EQ(1u, yPlane.mHorizSubsampling);
|
|
ASSERT_EQ(1u, yPlane.mVertSubsampling);
|
|
ASSERT_EQ(2u, uPlane.mHorizSubsampling);
|
|
ASSERT_EQ(2u, uPlane.mVertSubsampling);
|
|
ASSERT_EQ(2u, vPlane.mHorizSubsampling);
|
|
ASSERT_EQ(2u, vPlane.mVertSubsampling);
|
|
|
|
uint8_t *yRowPtr = clientBuffer->data() + yPlane.mOffset;
|
|
uint8_t *uRowPtr = clientBuffer->data() + uPlane.mOffset;
|
|
uint8_t *vRowPtr = clientBuffer->data() + vPlane.mOffset;
|
|
for (int32_t row = 0; row < kHeight; ++row) {
|
|
uint8_t *yPtr = yRowPtr;
|
|
uint8_t *uPtr = uRowPtr;
|
|
uint8_t *vPtr = vRowPtr;
|
|
for (int32_t col = 0; col < kWidth; ++col) {
|
|
ASSERT_EQ(((row + col) & 0x3) << 6, yPtr[0]);
|
|
ASSERT_EQ(((row + col) & 0x3FC) >> 2, yPtr[1]);
|
|
yPtr += yPlane.mColInc;
|
|
if (row < kHeight / 2 && col < kWidth / 2) {
|
|
ASSERT_EQ(((row + col + 1) & 0x3) << 6, uPtr[0]);
|
|
ASSERT_EQ(((row + col + 1) & 0x3FC) >> 2, uPtr[1]);
|
|
ASSERT_EQ(((row + col + 2) & 0x3) << 6, vPtr[0]);
|
|
ASSERT_EQ(((row + col + 2) & 0x3FC) >> 2, vPtr[1]);
|
|
uPtr += uPlane.mColInc;
|
|
vPtr += vPlane.mColInc;
|
|
}
|
|
}
|
|
yRowPtr += yPlane.mRowInc;
|
|
if (row < kHeight / 2) {
|
|
uRowPtr += uPlane.mRowInc;
|
|
vRowPtr += vPlane.mRowInc;
|
|
}
|
|
}
|
|
}
|
|
|
|
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));
|
|
});
|
|
|
|
TEST(LinearOutputBuffersTest, PcmConvertFormat) {
|
|
// Prepare LinearOutputBuffers
|
|
std::shared_ptr<LinearOutputBuffers> buffers =
|
|
std::make_shared<LinearOutputBuffers>("test");
|
|
sp<AMessage> format{new AMessage};
|
|
format->setInt32(KEY_CHANNEL_COUNT, 1);
|
|
format->setInt32(KEY_SAMPLE_RATE, 8000);
|
|
format->setInt32(KEY_PCM_ENCODING, kAudioEncodingPcmFloat);
|
|
format->setInt32("android._config-pcm-encoding", kAudioEncodingPcm16bit);
|
|
format->setInt32("android._codec-pcm-encoding", kAudioEncodingPcmFloat);
|
|
buffers->setFormat(format);
|
|
|
|
// Prepare a linear C2Buffer
|
|
std::shared_ptr<C2BlockPool> pool;
|
|
ASSERT_EQ(OK, GetCodec2BlockPool(C2BlockPool::BASIC_LINEAR, nullptr, &pool));
|
|
|
|
std::shared_ptr<C2LinearBlock> block;
|
|
ASSERT_EQ(OK, pool->fetchLinearBlock(
|
|
1024, C2MemoryUsage{C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}, &block));
|
|
std::shared_ptr<C2Buffer> c2Buffer =
|
|
C2Buffer::CreateLinearBuffer(block->share(0, 1024, C2Fence()));
|
|
|
|
// Test regular buffer convert
|
|
size_t index;
|
|
sp<MediaCodecBuffer> clientBuffer;
|
|
ASSERT_EQ(OK, buffers->registerBuffer(c2Buffer, &index, &clientBuffer));
|
|
int32_t pcmEncoding = 0;
|
|
ASSERT_TRUE(clientBuffer->format()->findInt32(KEY_PCM_ENCODING, &pcmEncoding));
|
|
EXPECT_EQ(kAudioEncodingPcm16bit, pcmEncoding);
|
|
ASSERT_TRUE(buffers->releaseBuffer(clientBuffer, &c2Buffer));
|
|
|
|
// Test null buffer convert
|
|
ASSERT_EQ(OK, buffers->registerBuffer(nullptr, &index, &clientBuffer));
|
|
ASSERT_TRUE(clientBuffer->format()->findInt32(KEY_PCM_ENCODING, &pcmEncoding));
|
|
EXPECT_EQ(kAudioEncodingPcm16bit, pcmEncoding);
|
|
ASSERT_TRUE(buffers->releaseBuffer(clientBuffer, &c2Buffer));
|
|
|
|
// Do the same test in the array mode
|
|
std::shared_ptr<OutputBuffersArray> array = buffers->toArrayMode(8);
|
|
|
|
// Test regular buffer convert
|
|
ASSERT_EQ(OK, buffers->registerBuffer(c2Buffer, &index, &clientBuffer));
|
|
ASSERT_TRUE(clientBuffer->format()->findInt32(KEY_PCM_ENCODING, &pcmEncoding));
|
|
EXPECT_EQ(kAudioEncodingPcm16bit, pcmEncoding);
|
|
ASSERT_TRUE(buffers->releaseBuffer(clientBuffer, &c2Buffer));
|
|
|
|
// Test null buffer convert
|
|
ASSERT_EQ(OK, buffers->registerBuffer(nullptr, &index, &clientBuffer));
|
|
ASSERT_TRUE(clientBuffer->format()->findInt32(KEY_PCM_ENCODING, &pcmEncoding));
|
|
EXPECT_EQ(kAudioEncodingPcm16bit, pcmEncoding);
|
|
ASSERT_TRUE(buffers->releaseBuffer(clientBuffer, &c2Buffer));
|
|
}
|
|
|
|
} // namespace android
|