* Copyright (C) 2026 Huawei Device Co., Ltd.
* 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 "preprocessor.h"
#include <algorithm>
#include <charconv>
#include <cmath>
#include <cstring>
#include <limits>
#include <optional>
#include <regex>
#include <string>
#include <vector>
#include "securec.h"
#include "avcodec_errors.h"
#include "avcodec_log.h"
#include "avcodeclist_impl.h"
#include "meta/meta_key.h"
#include "native_avcodec_base.h"
#include "syspara/parameters.h"
#include "v2_2/buffer_handle_meta_key_type.h"
namespace {
bool IsEvenNumber(int32_t value)
{
constexpr int32_t checkEvenNumber = 2;
return (value % checkEvenNumber == 0);
}
}
namespace OHOS {
namespace MediaAVCodec {
namespace {
using namespace OHOS;
namespace V2_2 = OHOS::HDI::Display::Graphic::Common::V2_2;
constexpr OHOS::HiviewDFX::HiLogLabel LABEL = {LOG_CORE, LOG_DOMAIN_FRAMEWORK, "Preprocessor"};
constexpr int32_t BUFFER_REQUEST_STRIDE_ALIGNMENT = 32;
constexpr double ROUND_TO_TWO_DECIMALS_FACTOR = 100.0;
constexpr int32_t RGBA_BYTES_PER_PIXEL = 4;
constexpr int32_t P010_BYTES_PER_SAMPLE = 2;
constexpr int32_t YUV_SUBSAMPLE = 2;
constexpr int32_t INVALID_ROW_BYTES = -1;
constexpr int32_t DISABLE_CROP_LEFT = 0;
constexpr int32_t DISABLE_CROP_TOP = 0;
constexpr int32_t DISABLE_CROP_RIGHT = 0;
constexpr int32_t DISABLE_CROP_BOTTOM = 0;
constexpr int32_t DISABLE_DOWNSAMPLING_WIDTH = 0;
constexpr int32_t DISABLE_DOWNSAMPLING_HEIGHT = 0;
constexpr int32_t DEFAULT_METADATA_OFFSET = 0;
constexpr double IDENTITY_METADATA_SCALE = 1.0;
constexpr size_t ROI_MATCH_TOP_INDEX = 1;
constexpr size_t ROI_MATCH_LEFT_INDEX = 2;
constexpr size_t ROI_MATCH_BOTTOM_INDEX = 3;
constexpr size_t ROI_MATCH_RIGHT_INDEX = 4;
constexpr size_t ROI_MATCH_PARAM_INDEX = 5;
auto fastKitEnable = OHOS::system::GetBoolParameter("persist.OHOS.MediaAVCodec.FastKit.Enable", true);
std::optional<int32_t> ParseInt32(const std::string &str)
{
int32_t value = 0;
auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), value);
return ec == std::errc{} && ptr == (str.data() + str.size()) ? std::optional<int32_t>(value) : std::nullopt;
}
double RoundToTwoDecimals(double value)
{
return std::round(value * ROUND_TO_TWO_DECIMALS_FACTOR) / ROUND_TO_TWO_DECIMALS_FACTOR;
}
bool IsYuv420Format(OHOS::GraphicPixelFormat format)
{
return format == GRAPHIC_PIXEL_FMT_YCBCR_420_SP || format == GRAPHIC_PIXEL_FMT_YCRCB_420_SP ||
format == GRAPHIC_PIXEL_FMT_YCBCR_P010 || format == GRAPHIC_PIXEL_FMT_YCRCB_P010 ||
format == GRAPHIC_PIXEL_FMT_YCBCR_420_P;
}
int32_t GetMinRowBytes(OHOS::GraphicPixelFormat format, int32_t width)
{
switch (format) {
case GRAPHIC_PIXEL_FMT_RGBA_8888:
case GRAPHIC_PIXEL_FMT_RGBA_1010102:
return width * RGBA_BYTES_PER_PIXEL;
case GRAPHIC_PIXEL_FMT_YCBCR_P010:
case GRAPHIC_PIXEL_FMT_YCRCB_P010:
return width * P010_BYTES_PER_SAMPLE;
case GRAPHIC_PIXEL_FMT_YCBCR_420_SP:
case GRAPHIC_PIXEL_FMT_YCRCB_420_SP:
case GRAPHIC_PIXEL_FMT_YCBCR_420_P:
return width;
default:
return INVALID_ROW_BYTES;
}
}
bool IsGeometrySupported(OHOS::GraphicPixelFormat format, int32_t width, int32_t height)
{
if (width <= 0 || height <= 0) {
return false;
}
if (fastKitEnable &&
!OHOS::MediaAVCodec::PreProcessing::FastKitsInterface::IsFormatSupported(static_cast<int32_t>(format))) {
return false;
} else if (!fastKitEnable &&
!OHOS::MediaAVCodec::PreProcessing::CpuImageProcessor::IsFormatSupported(static_cast<int32_t>(format))) {
return false;
}
return !IsYuv420Format(format) || (width % YUV_SUBSAMPLE == 0 && height % YUV_SUBSAMPLE == 0);
}
std::string TransformRoiMetadata(const std::string &roi, double scaleX, double scaleY,
int32_t offsetTop, int32_t offsetLeft)
{
static const std::regex roiPattern(R"((?:^|;)(-?\d+),(-?\d+)-(-?\d+),(-?\d+)(?:=([^;]*))?(?=;|$))");
std::smatch match;
std::string result;
for (std::string temp = roi; std::regex_search(temp, match, roiPattern); temp = match.suffix().str()) {
auto top = ParseInt32(match[ROI_MATCH_TOP_INDEX].str());
auto left = ParseInt32(match[ROI_MATCH_LEFT_INDEX].str());
auto bottom = ParseInt32(match[ROI_MATCH_BOTTOM_INDEX].str());
auto right = ParseInt32(match[ROI_MATCH_RIGHT_INDEX].str());
if (!top || !left || !bottom || !right) {
continue;
}
int32_t newTop = static_cast<int32_t>(std::round(*top * scaleY)) - offsetTop;
int32_t newLeft = static_cast<int32_t>(std::round(*left * scaleX)) - offsetLeft;
int32_t newBottom = static_cast<int32_t>(std::round(*bottom * scaleY)) - offsetTop;
int32_t newRight = static_cast<int32_t>(std::round(*right * scaleX)) - offsetLeft;
if (!result.empty()) {
result += ";";
}
result += std::to_string(newTop) + "," + std::to_string(newLeft) + "-" +
std::to_string(newBottom) + "," + std::to_string(newRight);
if (match[ROI_MATCH_PARAM_INDEX].matched) {
result += "=" + match[ROI_MATCH_PARAM_INDEX].str();
}
}
if (!result.empty()) {
result += ";";
}
return result;
}
}
Preprocessor::Preprocessor(const std::string &mime) : mime_(mime)
{
if (fastKitEnable) {
fastKitsInterface_.Retain();
}
}
Preprocessor::~Preprocessor()
{
if (fastKitEnable) {
fastKitsInterface_.Release();
}
}
bool Preprocessor::IsAvailable()
{
if (!fastKitEnable) {
return true;
}
auto &fastKits = PreProcessing::FastKitsInterface::GetInstance();
fastKits.Retain();
bool loaded = fastKits.IsLoaded();
fastKits.Release();
return loaded;
}
int32_t Preprocessor::ValidateConfiguration(const Media::Format &format, bool configuring)
{
CHECK_AND_RETURN_RET_LOG(UsingAsyncMode(format), AVCS_ERR_INVALID_VAL,
"Preprocessor encoder does not support synchronous mode!");
UpdateConfiguredValues(format, configuring);
int32_t ret = ValidateCrop(format);
ret = (ret == AVCS_ERR_OK) ? ValidateDownsampling(format) : ret;
ret = (ret == AVCS_ERR_OK) ? ValidateDropFrame(format) : ret;
if (ret != AVCS_ERR_OK) {
return ret;
}
if (cropParams_.result == ConfigResult::ENABLED && downsamplingParams_.result == ConfigResult::ENABLED) {
AVCODEC_LOGE("Crop and downsampling cannot be enabled simultaneously");
return AVCS_ERR_INVALID_VAL;
}
if (cropParams_.result == ConfigResult::ENABLED && IsYuv420Format(encoderParams_.pixelFormat)) {
int32_t cropWidth = cropParams_.right - cropParams_.left + 1;
int32_t cropHeight = cropParams_.bottom - cropParams_.top + 1;
bool invalid = (!IsEvenNumber(cropParams_.left) || !IsEvenNumber(cropParams_.top) ||
!IsEvenNumber(cropWidth) || !IsEvenNumber(cropHeight));
CHECK_AND_RETURN_RET_LOG(!invalid, AVCS_ERR_INVALID_VAL,
"For YUV pixel format input, crop left-top coordinates and crop width, height must be even numbers. "
"(left, top)=(%{public}d, %{public}d), width=%{public}d, height=%{public}d",
cropParams_.left, cropParams_.top, cropWidth, cropHeight);
}
if (downsamplingParams_.result == ConfigResult::ENABLED && IsYuv420Format(encoderParams_.pixelFormat)) {
bool invalid = !IsEvenNumber(downsamplingParams_.width) || !IsEvenNumber(downsamplingParams_.height);
CHECK_AND_RETURN_RET_LOG(!invalid, AVCS_ERR_INVALID_VAL,
"For YUV pixel format input, target width and height of downsampling must be even numbers."
"width=%{public}d, height=%{public}d", downsamplingParams_.width, downsamplingParams_.height);
}
return AVCS_ERR_OK;
}
GraphicPixelFormat Preprocessor::GetPixelFormatFromConfiguration(const Media::Format &format) const
{
int32_t pixelFormat = static_cast<int32_t>(VideoPixelFormat::NV12);
(void)format.GetIntValue(Media::Tag::VIDEO_PIXEL_FORMAT, pixelFormat);
int32_t profile = static_cast<int32_t>(HEVCProfile::HEVC_PROFILE_UNKNOW);
(void)format.GetIntValue(Media::Tag::MEDIA_PROFILE, profile);
return ConvertVideoPixelFormat2GraphicPixelFormat(static_cast<VideoPixelFormat>(pixelFormat), profile);
}
GraphicPixelFormat Preprocessor::GetPixelFormatFromParameter(const Media::Format &format) const
{
bool isConfigured10Bit = (encoderConfig_.pixelFormat == GraphicPixelFormat::GRAPHIC_PIXEL_FMT_YCBCR_P010 ||
encoderConfig_.pixelFormat == GraphicPixelFormat::GRAPHIC_PIXEL_FMT_YCRCB_P010 ||
encoderConfig_.pixelFormat == GraphicPixelFormat::GRAPHIC_PIXEL_FMT_RGBA_1010102 ||
encoderConfig_.pixelFormat == GraphicPixelFormat::GRAPHIC_PIXEL_FMT_RGBA_1010108 ||
encoderConfig_.pixelFormat == GraphicPixelFormat::GRAPHIC_PIXEL_FMT_RAW10);
int32_t profile = static_cast<int32_t>(HEVCProfile::HEVC_PROFILE_UNKNOW);
bool hasProfile = format.GetIntValue(Media::Tag::MEDIA_PROFILE, profile);
if (!hasProfile && isConfigured10Bit) {
profile = static_cast<int32_t>(HEVCProfile::HEVC_PROFILE_MAIN_10);
}
int32_t pixelFormat = static_cast<int32_t>(VideoPixelFormat::NV12);
bool hasPixelFormat = format.GetIntValue(Media::Tag::VIDEO_PIXEL_FORMAT, pixelFormat);
if (hasPixelFormat) {
return ConvertVideoPixelFormat2GraphicPixelFormat(static_cast<VideoPixelFormat>(pixelFormat), profile);
}
return encoderConfig_.pixelFormat;
}
int32_t Preprocessor::Configure(const Media::Format &format)
{
if (cropParams_.result != ConfigResult::NOT_SET) {
cropConfig_ = CropConfig(cropParams_);
}
if (downsamplingParams_.result != ConfigResult::NOT_SET) {
downsamplingConfig_ = DownsamplingConfig(downsamplingParams_);
}
if (dropParams_.result != ConfigResult::NOT_SET) {
dropFrameConfig_ = DropFrameConfig(dropParams_);
}
if (fastKitEnable && (cropConfig_.enabled || downsamplingConfig_.enabled)) {
int32_t initRet = fastKitsInterface_.Init(static_cast<uint32_t>(encoderParams_.pixelFormat));
CHECK_AND_RETURN_RET_LOG(initRet == AVCS_ERR_OK, initRet, "Failed to init FastKitsInterface: %{public}d",
initRet);
}
if (dropFrameConfig_.enabled) {
dropFilter_.Configure(dropFrameConfig_.targetFrameRate, encoderParams_.frameRate);
}
encoderConfig_.width = encoderParams_.width;
encoderConfig_.height = encoderParams_.height;
encoderConfig_.pixelFormat = encoderParams_.pixelFormat;
encoderConfig_.frameRate = encoderParams_.frameRate;
AVCODEC_LOGI("PreProcessor configured: crop=%{public}d, ds=%{public}d, drop=%{public}d", cropConfig_.enabled,
downsamplingConfig_.enabled, dropFrameConfig_.enabled);
return AVCS_ERR_OK;
}
bool Preprocessor::UsingAsyncMode(const Media::Format &format) const
{
int32_t asyncMode = 0;
(void)format.GetIntValue(Media::Tag::AV_CODEC_ENABLE_SYNC_MODE, asyncMode);
return asyncMode == 0;
}
int32_t Preprocessor::ValidateCrop(const Media::Format &format)
{
cropParams_.result = ConfigResult::NOT_SET;
int32_t left{0};
int32_t right{0};
int32_t top{0};
int32_t bottom{0};
bool hasLeft = format.GetIntValue(OH_MD_KEY_VIDEO_ENCODER_PREPROC_CROP_LEFT, left);
bool hasRight = format.GetIntValue(OH_MD_KEY_VIDEO_ENCODER_PREPROC_CROP_RIGHT, right);
bool hasTop = format.GetIntValue(OH_MD_KEY_VIDEO_ENCODER_PREPROC_CROP_TOP, top);
bool hasBottom = format.GetIntValue(OH_MD_KEY_VIDEO_ENCODER_PREPROC_CROP_BOTTOM, bottom);
bool hasAny = hasLeft || hasRight || hasTop || hasBottom;
bool hasAll = hasLeft && hasRight && hasTop && hasBottom;
if (!hasAny) {
return AVCS_ERR_OK;
}
CHECK_AND_RETURN_RET_LOG(IsAvailable(), AVCS_ERR_UNSUPPORT, "Crop are not supported");
CHECK_AND_RETURN_RET_LOG(!hasAny || hasAll, AVCS_ERR_INVALID_VAL,
"Incomplete crop parameters: hasLeft=%{public}d, hasRight=%{public}d, "
"hasTop=%{public}d, hasBottom=%{public}d",
hasLeft, hasRight, hasTop, hasBottom);
if (left == DISABLE_CROP_LEFT && top == DISABLE_CROP_TOP && right == DISABLE_CROP_RIGHT &&
bottom == DISABLE_CROP_BOTTOM) {
cropParams_.result = ConfigResult::DISABLED;
AVCODEC_LOGI("Crop disabled by zero crop parameters");
return AVCS_ERR_OK;
}
Range encWidth;
Range encHeight;
CHECK_AND_RETURN_RET_LOG(GetEncoderRange(format, encWidth, encHeight), AVCS_ERR_INVALID_VAL,
"Failed to get encoder capability for crop");
constexpr int32_t cropRectInclusiveOffset = 1;
int32_t cropWidth = right - left + cropRectInclusiveOffset;
int32_t cropHeight = bottom - top + cropRectInclusiveOffset;
bool rangeValid = (0 <= left && left < right && right < encoderParams_.width) &&
(0 <= top && top < bottom && bottom < encoderParams_.height) &&
(encWidth.InRange(cropWidth) && encHeight.InRange(cropHeight));
CHECK_AND_RETURN_RET_LOG(rangeValid, AVCS_ERR_INVALID_VAL,
"Invalid crop parameters: left=%{public}d, right=%{public}d, top=%{public}d, bottom=%{public}d",
left, right, top, bottom);
cropParams_.left = left;
cropParams_.right = right;
cropParams_.top = top;
cropParams_.bottom = bottom;
cropParams_.result = ConfigResult::ENABLED;
return AVCS_ERR_OK;
}
int32_t Preprocessor::ValidateDownsampling(const Media::Format &format)
{
downsamplingParams_.result = ConfigResult::NOT_SET;
int32_t width{0};
int32_t height{0};
bool hasWidth = format.GetIntValue(OH_MD_KEY_VIDEO_ENCODER_PREPROC_DOWNSAMPLING_WIDTH, width);
bool hasHeight = format.GetIntValue(OH_MD_KEY_VIDEO_ENCODER_PREPROC_DOWNSAMPLING_HEIGHT, height);
if (!hasWidth && !hasHeight) {
return AVCS_ERR_OK;
}
if (!IsAvailable()) {
AVCODEC_LOGE("Downsampling are not supported");
return AVCS_ERR_UNSUPPORT;
}
if (hasWidth != hasHeight) {
return AVCS_ERR_INVALID_VAL;
}
if (width == DISABLE_DOWNSAMPLING_WIDTH && height == DISABLE_DOWNSAMPLING_HEIGHT) {
downsamplingParams_.result = ConfigResult::DISABLED;
return AVCS_ERR_OK;
}
Range encWidth;
Range encHeight;
if (!GetEncoderRange(format, encWidth, encHeight)) {
AVCODEC_LOGE("Failed to get encoder capability for downsampling");
return AVCS_ERR_INVALID_VAL;
}
int32_t minWidth = std::max((dAlgoWidthMin_ > 0) ? dAlgoWidthMin_ : encWidth.minVal, encWidth.minVal);
int32_t maxWidth = (dAlgoWidthMax_ > 0) ? std::min(dAlgoWidthMax_, encWidth.maxVal) : encWidth.maxVal;
int32_t minHeight = std::max((dAlgoHeightMin_ > 0) ? dAlgoHeightMin_ : encHeight.minVal, encHeight.minVal);
int32_t maxHeight = (dAlgoHeightMax_ > 0) ? std::min(dAlgoHeightMax_, encHeight.maxVal) : encHeight.maxVal;
if (!(minWidth > 0 && minHeight > 0 && minWidth <= width && width <= encoderParams_.width &&
encoderParams_.width <= maxWidth && minHeight <= height && height <= encoderParams_.height &&
encoderParams_.height <= maxHeight)) {
AVCODEC_LOGE("Invalid downsampling parameters: width=%{public}d, height=%{public}d, "
"valid range: width [%{public}d, %{public}d], height [%{public}d, %{public}d], "
"encoder configured: %{public}dx%{public}d",
width, height, minWidth, maxWidth, minHeight, maxHeight, encoderParams_.width,
encoderParams_.height);
return AVCS_ERR_INVALID_VAL;
}
downsamplingParams_.width = width;
downsamplingParams_.height = height;
downsamplingParams_.result = ConfigResult::ENABLED;
return AVCS_ERR_OK;
}
int32_t Preprocessor::ValidateDropFrame(const Media::Format &format)
{
dropParams_.result = ConfigResult::NOT_SET;
double targetFrameRate = 0;
if (!format.GetDoubleValue(OH_MD_KEY_VIDEO_ENCODER_PREPROC_DROP_TO_FRAME_RATE, targetFrameRate)) {
return AVCS_ERR_OK;
}
targetFrameRate = RoundToTwoDecimals(targetFrameRate);
if (targetFrameRate < -std::numeric_limits<double>::epsilon() ||
encoderParams_.frameRate < -std::numeric_limits<double>::epsilon()) {
AVCODEC_LOGE("Invalid drop frame rate: %{public}.2f, configured=%{public}.2f", targetFrameRate,
encoderParams_.frameRate);
return AVCS_ERR_INVALID_VAL;
}
if (std::fabs(targetFrameRate) < std::numeric_limits<double>::epsilon()) {
dropParams_.result = ConfigResult::DISABLED;
return AVCS_ERR_OK;
}
if ((targetFrameRate - encoderParams_.frameRate) < -std::numeric_limits<double>::epsilon()) {
dropParams_.targetFrameRate = targetFrameRate;
dropParams_.result = ConfigResult::ENABLED;
return AVCS_ERR_OK;
}
AVCODEC_LOGE("Invalid drop frame rate: %{public}.2f, configured=%{public}.2f", targetFrameRate,
encoderParams_.frameRate);
return AVCS_ERR_INVALID_VAL;
}
int32_t Preprocessor::SetParameter(const Media::Format &format)
{
return Configure(format);
}
int32_t Preprocessor::Process(sptr<SurfaceBuffer> input, sptr<SurfaceBuffer> &output, uint64_t pts)
{
CHECK_AND_RETURN_RET_LOG(input && output, AVCS_ERR_INVALID_VAL,
"Invalid buffer: input is %{public}s, output is %{public}s", input ? "valid" : "nullptr",
output ? "valid" : "nullptr");
if (IsCropEnabled()) {
int32_t ret = Crop(input, output);
CHECK_AND_RETURN_RET_LOG(ret == AVCS_ERR_OK, ret, "Crop failed: %{public}d", ret);
UpdateOutputMetadata(input, output,
{IDENTITY_METADATA_SCALE, IDENTITY_METADATA_SCALE, cropConfig_.top,
cropConfig_.left});
return ret;
}
if (IsDownsamplingEnabled()) {
int32_t ret = Downsampling(input, output);
CHECK_AND_RETURN_RET_LOG(ret == AVCS_ERR_OK, ret, "Downsampling failed: %{public}d", ret);
UpdateOutputMetadata(input, output,
{static_cast<double>(output->GetWidth()) / input->GetWidth(),
static_cast<double>(output->GetHeight()) / input->GetHeight(),
DEFAULT_METADATA_OFFSET, DEFAULT_METADATA_OFFSET});
return ret;
}
int32_t ret = Copy(input, output);
CHECK_AND_RETURN_RET_LOG(ret == AVCS_ERR_OK, ret, "Copy failed: %{public}d", ret);
UpdateOutputMetadata(input, output,
{IDENTITY_METADATA_SCALE, IDENTITY_METADATA_SCALE, DEFAULT_METADATA_OFFSET,
DEFAULT_METADATA_OFFSET});
return ret;
}
bool Preprocessor::ShouldDropFrame(uint64_t pts)
{
if (!dropFrameConfig_.enabled) {
return false;
}
return dropFilter_.ShouldDropFrame(pts);
}
void Preprocessor::FlushTimeStamp()
{
if (!dropFrameConfig_.enabled) {
return;
}
return dropFilter_.FlushTimeStamp();
}
void Preprocessor::SetOnBufferReallocCallback(std::function<void(sptr<SurfaceBuffer>, sptr<SurfaceBuffer>)> callback)
{
onOutSurfaceBufferReallocCb_ = callback;
}
bool Preprocessor::IsCropEnabled() const
{
return cropConfig_.enabled;
}
bool Preprocessor::IsDownsamplingEnabled() const
{
return downsamplingConfig_.enabled;
}
const Preprocessor::CropConfig &Preprocessor::GetCropConfig() const
{
return cropConfig_;
}
const Preprocessor::DownsamplingConfig &Preprocessor::GetDownsamplingConfig() const
{
return downsamplingConfig_;
}
const Preprocessor::DropFrameConfig &Preprocessor::GetDropFrameConfig() const
{
return dropFrameConfig_;
}
int32_t Preprocessor::Crop(sptr<SurfaceBuffer> input, sptr<SurfaceBuffer> &output)
{
if (!CheckOrReallocOutSurfaceBuffer(cropConfig_.width, cropConfig_.height, input->GetFormat(), output)) {
AVCODEC_LOGE("Realloc buffer failed");
return AVCS_ERR_NO_MEMORY;
}
if (fastKitEnable) {
PreProcessing::CropRect rect{cropConfig_.top, cropConfig_.left, cropConfig_.bottom, cropConfig_.right};
int32_t ret = fastKitsInterface_.Crop(input, output, rect);
CHECK_AND_RETURN_RET_LOG(ret == AVCS_ERR_OK, ret, "FastKit crop failed: %{public}d", ret);
return AVCS_ERR_OK;
}
PreProcessing::CpuImageProcessRequest request;
request.input = input;
request.output = output;
request.operation = PreProcessing::CpuImageOperation::CROP;
request.crop = {cropConfig_.left, cropConfig_.top, cropConfig_.width, cropConfig_.height};
int32_t ret = PreProcessing::CpuImageProcessor::Process(request);
CHECK_AND_RETURN_RET_LOG(ret == AVCS_ERR_OK, ret, "Crop failed: %{public}d", ret);
return AVCS_ERR_OK;
}
int32_t Preprocessor::Downsampling(sptr<SurfaceBuffer> input, sptr<SurfaceBuffer> &output)
{
if (!CheckOrReallocOutSurfaceBuffer(downsamplingConfig_.width, downsamplingConfig_.height,
input->GetFormat(), output)) {
AVCODEC_LOGE("Realloc buffer failed");
return AVCS_ERR_NO_MEMORY;
}
if (fastKitEnable) {
int32_t ret = fastKitsInterface_.DownSample(input, output);
CHECK_AND_RETURN_RET_LOG(ret == AVCS_ERR_OK, ret, "FastKit downsampling failed: %{public}d", ret);
return AVCS_ERR_OK;
}
PreProcessing::CpuImageProcessRequest request;
request.input = input;
request.output = output;
request.operation = PreProcessing::CpuImageOperation::DOWNSAMPLE;
int32_t ret = PreProcessing::CpuImageProcessor::Process(request);
CHECK_AND_RETURN_RET_LOG(ret == AVCS_ERR_OK, ret, "Downsampling failed: %{public}d", ret);
return AVCS_ERR_OK;
}
int32_t Preprocessor::Copy(sptr<SurfaceBuffer> input, sptr<SurfaceBuffer> &output)
{
if (!CheckOrReallocOutSurfaceBuffer(input->GetWidth(), input->GetHeight(), input->GetFormat(), output)) {
AVCODEC_LOGE("Realloc buffer failed");
return AVCS_ERR_NO_MEMORY;
}
if (fastKitEnable) {
PreProcessing::CropRect rect{0, 0, input->GetHeight() - 1, input->GetWidth() - 1};
int32_t ret = fastKitsInterface_.Crop(input, output, rect);
#ifdef SUPPORT_VIDEO_ENCODER_PREPROCESSING
CHECK_AND_RETURN_RET_LOG(ret == AVCS_ERR_OK, ret, "FastKit copy failed: %{public}d", ret);
return AVCS_ERR_OK;
#else
if (ret == AVCS_ERR_OK) {
return AVCS_ERR_OK;
}
#endif
}
PreProcessing::CpuImageProcessRequest request;
request.input = input;
request.output = output;
request.operation = PreProcessing::CpuImageOperation::COPY;
int32_t ret = PreProcessing::CpuImageProcessor::Process(request);
CHECK_AND_RETURN_RET_LOG(ret == AVCS_ERR_OK, ret, "Copy failed: %{public}d", ret);
return AVCS_ERR_OK;
}
void Preprocessor::UpdateOutputMetadata(const sptr<SurfaceBuffer> &input, const sptr<SurfaceBuffer> &output,
const RoiTransform &roiTransform)
{
CopyInputMetadata(input, output);
UpdateRoiMetadata(input, output, roiTransform);
}
void Preprocessor::CopyInputMetadata(const sptr<SurfaceBuffer> &input, const sptr<SurfaceBuffer> &output)
{
CHECK_AND_RETURN_LOG(input != nullptr && output != nullptr, "Invalid buffer for metadata copy");
std::vector<uint32_t> keys;
GSError ret = input->ListMetadataKeys(keys);
if (ret != GSERROR_OK) {
return;
}
for (uint32_t key : keys) {
if (key == V2_2::ATTRKEY_ROI_METADATA) {
continue;
}
std::vector<uint8_t> value;
ret = input->GetMetadata(key, value);
if (ret != GSERROR_OK) {
AVCODEC_LOGW("Get metadata failed: key=%{public}u, ret=%{public}d", key, ret);
continue;
}
ret = output->SetMetadata(key, value, false);
if (ret != GSERROR_OK) {
AVCODEC_LOGW("Set metadata failed: key=%{public}u, ret=%{public}d", key, ret);
}
}
}
void Preprocessor::UpdateRoiMetadata(const sptr<SurfaceBuffer> &input, const sptr<SurfaceBuffer> &output,
const RoiTransform &roiTransform)
{
CHECK_AND_RETURN_LOG(input != nullptr && output != nullptr, "Invalid buffer for roi metadata!");
std::vector<uint8_t> roiData;
if (input->GetMetadata(V2_2::ATTRKEY_ROI_METADATA, roiData) != GSERROR_OK || roiData.empty()) {
return;
}
auto nullPos = std::find(roiData.begin(), roiData.end(), '\0');
std::string roi(roiData.begin(), nullPos);
std::string transformed = TransformRoiMetadata(roi, roiTransform.scaleX, roiTransform.scaleY,
roiTransform.offsetTop, roiTransform.offsetLeft);
std::vector<uint8_t> outputRoi(transformed.begin(), transformed.end());
outputRoi.push_back('\0');
GSError ret = output->SetMetadata(V2_2::ATTRKEY_ROI_METADATA, outputRoi, false);
CHECK_AND_RETURN_LOG(ret == GSERROR_OK, "Set roi metadata failed: %{public}d", ret);
}
bool Preprocessor::IsHevc10BitData(HEVCProfile profile) const
{
return profile == HEVC_PROFILE_MAIN_10 && mime_ == "video/hevc";
}
GraphicPixelFormat Preprocessor::ConvertVideoPixelFormat2GraphicPixelFormat(VideoPixelFormat pixelFormat,
int32_t profile) const
{
bool is10BitData = IsHevc10BitData(static_cast<HEVCProfile>(profile));
switch (pixelFormat) {
case VideoPixelFormat::YUVI420:
return GraphicPixelFormat::GRAPHIC_PIXEL_FMT_YCBCR_420_P;
case VideoPixelFormat::RGBA:
return is10BitData ? GraphicPixelFormat::GRAPHIC_PIXEL_FMT_RGBA_1010102
: GraphicPixelFormat::GRAPHIC_PIXEL_FMT_RGBA_8888;
case VideoPixelFormat::NV21:
return is10BitData ? GraphicPixelFormat::GRAPHIC_PIXEL_FMT_YCRCB_P010
: GraphicPixelFormat::GRAPHIC_PIXEL_FMT_YCRCB_420_SP;
case VideoPixelFormat::RGBA1010102:
return GraphicPixelFormat::GRAPHIC_PIXEL_FMT_RGBA_1010102;
case VideoPixelFormat::NV12:
default:
return is10BitData ? GraphicPixelFormat::GRAPHIC_PIXEL_FMT_YCBCR_P010
: GraphicPixelFormat::GRAPHIC_PIXEL_FMT_YCBCR_420_SP;
}
}
void Preprocessor::UpdateConfiguredValues(const Media::Format &format, bool configuring)
{
int32_t configuredWidth = 0;
int32_t configuredHeight = 0;
double configuredFramerate = 0;
if (format.GetIntValue(Media::Tag::VIDEO_WIDTH, configuredWidth) &&
format.GetIntValue(Media::Tag::VIDEO_HEIGHT, configuredHeight)) {
encoderParams_.width = configuredWidth;
encoderParams_.height = configuredHeight;
}
if (format.GetDoubleValue(Media::Tag::VIDEO_FRAME_RATE, configuredFramerate)) {
encoderParams_.frameRate = RoundToTwoDecimals(configuredFramerate);
}
encoderParams_.pixelFormat =
configuring ? GetPixelFormatFromConfiguration(format) : GetPixelFormatFromParameter(format);
}
bool Preprocessor::GetEncoderRange(const Media::Format &format, Range &encWidth, Range &encHeight)
{
AVCodecListImpl codecList;
(void)codecList.Init();
CapabilityData *cap = codecList.GetCapability(std::string(mime_.c_str()), true, AVCodecCategory::AVCODEC_NONE);
if (cap == nullptr) {
return false;
}
encWidth = cap->width;
encHeight = cap->height;
return true;
}
bool Preprocessor::CheckOrReallocOutSurfaceBuffer(int32_t targetWidth, int32_t targetHeight,
int32_t targetFormat, sptr<SurfaceBuffer> &output)
{
auto targetGraphicFormat = static_cast<GraphicPixelFormat>(targetFormat);
if (!IsGeometrySupported(targetGraphicFormat, targetWidth, targetHeight)) {
AVCODEC_LOGE("Unsupported target geometry: %{public}dx%{public}d format=%{public}d",
targetWidth, targetHeight, targetFormat);
return false;
}
int32_t minStride = GetMinRowBytes(targetGraphicFormat, targetWidth);
if (minStride <= 0) {
return false;
}
bool needRealloc = !output || output->GetWidth() != targetWidth || output->GetHeight() != targetHeight ||
output->GetFormat() != targetFormat ||
output->GetVirAddr() == nullptr ||
output->GetStride() < minStride;
if (needRealloc) {
sptr<SurfaceBuffer> newBuffer = SurfaceBuffer::Create();
if (!newBuffer) {
return false;
}
constexpr auto DEFAULT_USAGE = BUFFER_USAGE_CPU_READ | BUFFER_USAGE_CPU_WRITE | BUFFER_USAGE_MEM_MMZ_CACHE;
BufferRequestConfig config = {
.width = targetWidth,
.height = targetHeight,
.strideAlignment = BUFFER_REQUEST_STRIDE_ALIGNMENT,
.format = targetFormat,
.usage = output ? output->GetUsage() | DEFAULT_USAGE : DEFAULT_USAGE,
.timeout = 0,
};
auto ret = newBuffer->Alloc(config);
if (ret != GSERROR_OK) {
AVCODEC_LOGE("Alloc output buffer failed: %{public}d", ret);
return false;
}
if (output && onOutSurfaceBufferReallocCb_) {
onOutSurfaceBufferReallocCb_(output, newBuffer);
}
output = newBuffer;
}
return true;
}
int32_t Preprocessor::GetConfiguredWidth() const
{
return encoderConfig_.width;
}
int32_t Preprocessor::GetConfiguredHeight() const
{
return encoderConfig_.height;
}
GraphicPixelFormat Preprocessor::GetConfiguredPixelFormat() const
{
return encoderConfig_.pixelFormat;
}
int32_t Preprocessor::GetOutputWidth() const
{
if (IsCropEnabled()) {
return cropConfig_.width;
}
if (IsDownsamplingEnabled()) {
return downsamplingConfig_.width;
}
return encoderConfig_.width;
}
int32_t Preprocessor::GetOutputHeight() const
{
if (IsCropEnabled()) {
return cropConfig_.height;
}
if (IsDownsamplingEnabled()) {
return downsamplingConfig_.height;
}
return encoderConfig_.height;
}
int32_t Preprocessor::GetCropLeft() const
{
return cropConfig_.left;
}
int32_t Preprocessor::GetCropRight() const
{
return cropConfig_.right;
}
int32_t Preprocessor::GetCropTop() const
{
return cropConfig_.top;
}
int32_t Preprocessor::GetCropBottom() const
{
return cropConfig_.bottom;
}
int32_t Preprocessor::GetDownsamplingWidth() const
{
return downsamplingConfig_.width;
}
int32_t Preprocessor::GetDownsamplingHeight() const
{
return downsamplingConfig_.height;
}
}
}