blob: cc80de655b3af664916699fb78448bebf23f5695 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//#define LOG_NDEBUG 0
#define LOG_TAG "C2VEAFormatConverter"
#include <C2VDACommon.h> // for HalPixelFormat
#include <C2VEAFormatConverter.h>
#include <C2AllocatorGralloc.h>
#include <C2PlatformSupport.h>
#include <android/hardware/graphics/common/1.0/types.h>
#include <utils/Log.h>
#include <inttypes.h>
#include <libyuv.h>
#include <memory>
#include <string>
using android::hardware::graphics::common::V1_0::BufferUsage;
namespace android {
namespace {
// The constant expression of mapping the pixel format conversion pair (src, dst) to a unique
// integer.
constexpr int convertMap(media::VideoPixelFormat src, media::VideoPixelFormat dst) {
return static_cast<int>(src) * (static_cast<int>(
media::VideoPixelFormat::PIXEL_FORMAT_MAX) + 1) + static_cast<int>(dst);
}
// The helper function to copy a plane pixel by pixel. It assumes bytesPerPixel is 1.
void copyPlaneByPixel(const uint8_t* src, int srcStride, int srcColInc, uint8_t* dst, int dstStride,
int dstColInc, int width, int height) {
for (int row = 0; row < height; row++) {
const uint8_t* srcRow = src;
uint8_t* dstRow = dst;
for (int col = 0; col < width; col++) {
memcpy(dstRow, srcRow, 1);
srcRow += srcColInc;
dstRow += dstColInc;
}
src += srcStride;
dst += dstStride;
}
}
} // namespace
// static
std::unique_ptr<C2VEAFormatConverter> C2VEAFormatConverter::Create(
media::VideoPixelFormat outFormat, const media::Size& visibleSize, uint32_t inputCount,
const media::Size& codedSize) {
if (outFormat != media::VideoPixelFormat::PIXEL_FORMAT_I420 &&
outFormat != media::VideoPixelFormat::PIXEL_FORMAT_NV12) {
ALOGE("Unsupported output format: %d", static_cast<int32_t>(outFormat));
return nullptr;
}
std::unique_ptr<C2VEAFormatConverter> converter(new C2VEAFormatConverter);
if (converter->initialize(outFormat, visibleSize, inputCount, codedSize) != C2_OK) {
ALOGE("Failed to initialize C2VEAFormatConverter");
return nullptr;
}
return converter;
}
c2_status_t C2VEAFormatConverter::initialize(media::VideoPixelFormat outFormat,
const media::Size& visibleSize, uint32_t inputCount,
const media::Size& codedSize) {
ALOGV("initialize(out_format=%s, visible_size=%dx%d, input_count=%u, coded_size=%dx%d)",
media::VideoPixelFormatToString(outFormat).c_str(), visibleSize.width(),
visibleSize.height(), inputCount, codedSize.width(), codedSize.height());
std::shared_ptr<C2BlockPool> pool;
c2_status_t status = GetCodec2BlockPool(C2BlockPool::BASIC_GRAPHIC, nullptr, &pool);
if (status != C2_OK) {
ALOGE("Failed to get basic graphic block pool (err=%d)", status);
return status;
}
HalPixelFormat halFormat;
if (outFormat == media::VideoPixelFormat::PIXEL_FORMAT_I420) {
// Android HAL format doesn't have I420, we use YV12 instead and swap U and V data while
// conversion to perform I420.
halFormat = HalPixelFormat::YV12;
} else {
halFormat = HalPixelFormat::YCbCr_420_888; // will allocate NV12 by minigbm.
}
uint32_t bufferCount = std::max(inputCount, kMinInputBufferCount);
for (uint32_t i = 0; i < bufferCount; i++) {
std::shared_ptr<C2GraphicBlock> block;
status = pool->fetchGraphicBlock(
codedSize.width(), codedSize.height(), static_cast<uint32_t>(halFormat),
{(C2MemoryUsage::CPU_READ|C2MemoryUsage::CPU_WRITE),
static_cast<uint64_t>(BufferUsage::VIDEO_ENCODER)}, &block);
if (status != C2_OK) {
ALOGE("Failed to fetch graphic block (err=%d)", status);
return status;
}
mGraphicBlocks.emplace_back(new BlockEntry(std::move(block)));
mAvailableQueue.push(mGraphicBlocks.back().get());
}
mOutFormat = outFormat;
mVisibleSize = visibleSize;
mTempPlaneU = std::unique_ptr<uint8_t[]>(
new uint8_t[mVisibleSize.width() * mVisibleSize.height() / 4]);
mTempPlaneV = std::unique_ptr<uint8_t[]>(
new uint8_t[mVisibleSize.width() * mVisibleSize.height() / 4]);
return C2_OK;
}
C2ConstGraphicBlock C2VEAFormatConverter::convertBlock(uint64_t frameIndex,
const C2ConstGraphicBlock& inputBlock,
c2_status_t* status) {
if (!isReady()) {
ALOGV("There is no available block for conversion");
*status = C2_NO_MEMORY;
return inputBlock; // This is actually redundant and should not be used.
}
BlockEntry* entry = mAvailableQueue.front();
std::shared_ptr<C2GraphicBlock> outputBlock = entry->mBlock;
const C2GraphicView& inputView = inputBlock.map().get();
C2PlanarLayout inputLayout = inputView.layout();
C2GraphicView outputView = outputBlock->map().get();
C2PlanarLayout outputLayout = outputView.layout();
uint8_t* dstY = outputView.data()[C2PlanarLayout::PLANE_Y];
uint8_t* dstU = outputView.data()[C2PlanarLayout::PLANE_V]; // only for I420
uint8_t* dstV = outputView.data()[C2PlanarLayout::PLANE_U]; // only for I420
uint8_t* dstUV = outputView.data()[C2PlanarLayout::PLANE_U]; // only for NV12
const int dstStrideY = outputLayout.planes[C2PlanarLayout::PLANE_Y].rowInc;
const int dstStrideU = outputLayout.planes[C2PlanarLayout::PLANE_V].rowInc; // only for I420
const int dstStrideV = outputLayout.planes[C2PlanarLayout::PLANE_U].rowInc; // only for I420
const int dstStrideUV = outputLayout.planes[C2PlanarLayout::PLANE_U].rowInc; // only for NV12
media::VideoPixelFormat inputFormat = media::VideoPixelFormat::PIXEL_FORMAT_UNKNOWN;
*status = C2_OK;
if (inputLayout.type == C2PlanarLayout::TYPE_YUV) {
const uint8_t* srcY = inputView.data()[C2PlanarLayout::PLANE_Y];
const uint8_t* srcU = inputView.data()[C2PlanarLayout::PLANE_U];
const uint8_t* srcV = inputView.data()[C2PlanarLayout::PLANE_V];
const int srcStrideY = inputLayout.planes[C2PlanarLayout::PLANE_Y].rowInc;
const int srcStrideU = inputLayout.planes[C2PlanarLayout::PLANE_U].rowInc;
const int srcStrideV = inputLayout.planes[C2PlanarLayout::PLANE_V].rowInc;
if (inputLayout.rootPlanes == 3) {
inputFormat = media::VideoPixelFormat::PIXEL_FORMAT_YV12;
} else if (inputLayout.rootPlanes == 2) {
inputFormat = (srcV > srcU) ? media::VideoPixelFormat::PIXEL_FORMAT_NV12
: media::VideoPixelFormat::PIXEL_FORMAT_NV21;
}
if (inputFormat == mOutFormat) {
ALOGV("Zero-Copy is applied");
mGraphicBlocks.emplace_back(new BlockEntry(frameIndex));
return inputBlock;
}
switch (convertMap(inputFormat, mOutFormat)) {
case convertMap(media::VideoPixelFormat::PIXEL_FORMAT_YV12,
media::VideoPixelFormat::PIXEL_FORMAT_I420):
libyuv::I420Copy(srcY, srcStrideY, srcU, srcStrideU, srcV, srcStrideV, dstY, dstStrideY,
dstU, dstStrideU, dstV, dstStrideV, mVisibleSize.width(),
mVisibleSize.height());
break;
case convertMap(media::VideoPixelFormat::PIXEL_FORMAT_YV12,
media::VideoPixelFormat::PIXEL_FORMAT_NV12):
libyuv::I420ToNV12(srcY, srcStrideY, srcU, srcStrideU, srcV, srcStrideV, dstY,
dstStrideY, dstUV, dstStrideUV, mVisibleSize.width(),
mVisibleSize.height());
break;
case convertMap(media::VideoPixelFormat::PIXEL_FORMAT_NV12,
media::VideoPixelFormat::PIXEL_FORMAT_I420):
libyuv::NV12ToI420(srcY, srcStrideY, srcU, srcStrideU, dstY, dstStrideY, dstU,
dstStrideU, dstV, dstStrideV, mVisibleSize.width(),
mVisibleSize.height());
break;
case convertMap(media::VideoPixelFormat::PIXEL_FORMAT_NV21,
media::VideoPixelFormat::PIXEL_FORMAT_I420):
libyuv::NV21ToI420(srcY, srcStrideY, srcV, srcStrideV, dstY, dstStrideY, dstU,
dstStrideU, dstV, dstStrideV, mVisibleSize.width(),
mVisibleSize.height());
break;
case convertMap(media::VideoPixelFormat::PIXEL_FORMAT_NV21,
media::VideoPixelFormat::PIXEL_FORMAT_NV12):
libyuv::CopyPlane(srcY, srcStrideY, dstY, dstStrideY, mVisibleSize.width(),
mVisibleSize.height());
copyPlaneByPixel(srcU, srcStrideU, 2, dstUV, dstStrideUV, 2,
mVisibleSize.width() / 2, mVisibleSize.height() / 2);
copyPlaneByPixel(srcV, srcStrideV, 2, dstUV + 1, dstStrideUV, 2,
mVisibleSize.width() / 2, mVisibleSize.height() / 2);
break;
default:
ALOGE("Unsupported pixel format conversion from %s to %s",
media::VideoPixelFormatToString(inputFormat).c_str(),
media::VideoPixelFormatToString(mOutFormat).c_str());
*status = C2_CORRUPTED;
return inputBlock; // This is actually redundant and should not be used.
}
} else if (inputLayout.type == C2PlanarLayout::TYPE_RGB) {
// There is only RGBA_8888 specified in C2AllocationGralloc::map(), no BGRA_8888. Maybe
// BGRA_8888 is not used now?
inputFormat = media::VideoPixelFormat::PIXEL_FORMAT_ABGR;
const uint8_t* srcRGB = inputView.data()[C2PlanarLayout::PLANE_R];
const int srcStrideRGB = inputLayout.planes[C2PlanarLayout::PLANE_R].rowInc;
switch (convertMap(inputFormat, mOutFormat)) {
case convertMap(media::VideoPixelFormat::PIXEL_FORMAT_ABGR,
media::VideoPixelFormat::PIXEL_FORMAT_I420):
libyuv::ABGRToI420(srcRGB, srcStrideRGB, dstY, dstStrideY, dstU, dstStrideU, dstV,
dstStrideV, mVisibleSize.width(), mVisibleSize.height());
break;
case convertMap(media::VideoPixelFormat::PIXEL_FORMAT_ABGR,
media::VideoPixelFormat::PIXEL_FORMAT_NV12):
{
// There is no libyuv function to convert ABGR to NV12. Therefore, we first convert to
// I420 on dst-Y plane and temporary U/V plane. Then we copy U and V pixels from
// temporary planes to dst-UV interleavedly.
const int tempStride = mVisibleSize.width() / 2;
libyuv::ABGRToI420(srcRGB, srcStrideRGB, dstY, dstStrideY, mTempPlaneU.get(),
tempStride, mTempPlaneV.get(), tempStride, mVisibleSize.width(),
mVisibleSize.height());
libyuv::MergeUVPlane(mTempPlaneU.get(), tempStride, mTempPlaneV.get(), tempStride,
dstUV, dstStrideUV, mVisibleSize.width() / 2,
mVisibleSize.height() / 2);
break;
}
default:
ALOGE("Unsupported pixel format conversion from %s to %s",
media::VideoPixelFormatToString(inputFormat).c_str(),
media::VideoPixelFormatToString(mOutFormat).c_str());
*status = C2_CORRUPTED;
return inputBlock; // This is actually redundant and should not be used.
}
} else {
ALOGE("Unsupported input layout type");
*status = C2_CORRUPTED;
return inputBlock; // This is actually redundant and should not be used.
}
ALOGV("convertBlock(frame_index=%" PRIu64 ", format=%s)", frameIndex,
media::VideoPixelFormatToString(inputFormat).c_str());
entry->mAssociatedFrameIndex = frameIndex;
mAvailableQueue.pop();
return outputBlock->share(C2Rect(mVisibleSize.width(), mVisibleSize.height()), C2Fence());
}
c2_status_t C2VEAFormatConverter::returnBlock(uint64_t frameIndex) {
ALOGV("returnBlock(frame_index=%" PRIu64 ")", frameIndex);
auto iter = std::find_if(
mGraphicBlocks.begin(), mGraphicBlocks.end(),
[frameIndex](const std::unique_ptr<BlockEntry>& be) {
return be->mAssociatedFrameIndex == frameIndex; });
if (iter == mGraphicBlocks.end()) {
ALOGE("Failed to find graphic block by converted/zero-copied frame index: %" PRIu64 "",
frameIndex);
return C2_BAD_INDEX;
}
if ((*iter)->mBlock) {
// Returned block is format converted.
(*iter)->mAssociatedFrameIndex = kNoFrameAssociated;
mAvailableQueue.push(iter->get());
} else {
// Returned block is zero-copied.
mGraphicBlocks.erase(iter);
}
return C2_OK;
}
} // namespace android