* 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 "native_avcodec_videobase.h"
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdint>
#include "securec.h"
#include "avcodec_log.h"
#ifdef __cplusplus
extern "C" {
#endif
const char *OH_MD_KEY_VIDEO_METADATA_ROI_TOP = "video_metadata_roi_top";
const char *OH_MD_KEY_VIDEO_METADATA_ROI_LEFT = "video_metadata_roi_left";
const char *OH_MD_KEY_VIDEO_METADATA_ROI_BOTTOM = "video_metadata_roi_bottom";
const char *OH_MD_KEY_VIDEO_METADATA_ROI_RIGHT = "video_metadata_roi_right";
const char *OH_MD_KEY_VIDEO_METADATA_ROI_DELTA_QP = "video_metadata_roi_deltaqp";
const char *OH_MD_KEY_VIDEO_METADATA_ROI_SEM_LABEL = "video_metadata_roi_sem_label";
#ifdef __cplusplus
}
#endif
namespace {
constexpr OHOS::HiviewDFX::HiLogLabel LABEL = {LOG_CORE, LOG_DOMAIN_FRAMEWORK, "NativeAvcodecVideoBase"};
struct KvDesc {
const char *keyName;
const char *key;
int keyNameLen;
int32_t minVal;
int32_t maxVal;
};
const KvDesc ROI_KV_TABLE[] = {
{ "dqp", OH_MD_KEY_VIDEO_METADATA_ROI_DELTA_QP, 3, -51, 51 },
{ "slb", OH_MD_KEY_VIDEO_METADATA_ROI_SEM_LABEL, 3, 0, 1 },
};
constexpr uint32_t ROI_KV_COUNT = sizeof(ROI_KV_TABLE) / sizeof(ROI_KV_TABLE[0]);
constexpr size_t ROI_STR_MAX_LEN = 256;
constexpr size_t ROI_SEGMENT_BUF_SIZE = 128;
struct RoiRectangleData {
int32_t top;
int32_t left;
int32_t bottom;
int32_t right;
};
}
class RoiStringHelper {
public:
static OH_AVErrCode AppendRoiString(char **roiStrInOut, OH_AVFormat *format);
static OH_AVErrCode GetRoiCount(const char *roiStr, uint32_t *outCount);
static OH_AVErrCode ParseRoiString(const char *roiStr, OH_AVFormat **outFormats,
uint32_t maxCapacity, uint32_t *outCount);
private:
RoiStringHelper() = delete;
~RoiStringHelper() = delete;
static bool ParseInt32(const char *str, int32_t *outVal, const char **outEndPtr);
static bool ParseRectangleSegment(const char *start, RoiRectangleData *data, const char **outEndPtr);
static bool ValidateRoiFormat(OH_AVFormat *format);
static uint32_t CountExistingRois(const char *roiStr);
static int BuildRoiSegmentStr(char *buffer, size_t bufferSize, OH_AVFormat *avFormat);
static OH_AVErrCode AppendSegmentToRoiStr(char **roiStrInOut, const char *segment,
size_t oldLen, size_t extraLen);
static bool ValidateAndParseRoiKvpairs(OH_AVFormat *avFormat, const char *eqPos, const char *segEnd);
static bool ParseOldFormatDqp(OH_AVFormat *avFormat, const char *eqPos);
static OH_AVFormat *ParseOneRoiSegmentToFormat(const char *start, const char *segEnd);
static bool IsSameRoiFormat(OH_AVFormat *a, OH_AVFormat *b);
static uint32_t DedupFormats(OH_AVFormat **formats, uint32_t count);
};
bool RoiStringHelper::ParseInt32(const char *str, int32_t *outVal, const char **outEndPtr)
{
errno = 0;
char *endPtr = nullptr;
long val = strtol(str, &endPtr, 10);
if (errno == ERANGE || endPtr == str || val < INT32_MIN || val > INT32_MAX) {
return false;
}
*outVal = static_cast<int32_t>(val);
*outEndPtr = endPtr;
return true;
}
bool RoiStringHelper::ValidateRoiFormat(OH_AVFormat *format)
{
int32_t top = 0;
int32_t left = 0;
int32_t bottom = 0;
int32_t right = 0;
if (!OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_METADATA_ROI_TOP, &top) ||
!OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_METADATA_ROI_LEFT, &left) ||
!OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_METADATA_ROI_BOTTOM, &bottom) ||
!OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_METADATA_ROI_RIGHT, &right)) {
return false;
}
if (top < 0 || left < 0 || bottom < 0 || right < 0 || top >= bottom || left >= right) {
return false;
}
for (uint32_t i = 0; i < ROI_KV_COUNT; i++) {
int32_t kvVal = 0;
if (OH_AVFormat_GetIntValue(format, ROI_KV_TABLE[i].key, &kvVal)) {
if (kvVal < ROI_KV_TABLE[i].minVal || kvVal > ROI_KV_TABLE[i].maxVal) {
return false;
}
}
}
return true;
}
bool RoiStringHelper::ParseRectangleSegment(const char *start, RoiRectangleData *data, const char **outEndPtr)
{
const char *endPtr = nullptr;
if (!ParseInt32(start, &data->top, &endPtr) || *endPtr != ',') {
return false;
}
if (!ParseInt32(endPtr + 1, &data->left, &endPtr) || *endPtr != '-') {
return false;
}
if (!ParseInt32(endPtr + 1, &data->bottom, &endPtr) || *endPtr != ',') {
return false;
}
if (!ParseInt32(endPtr + 1, &data->right, &endPtr)) {
return false;
}
char nextChar = *endPtr;
if (nextChar != '=' && nextChar != ';' && nextChar != '\0') {
return false;
}
if (data->top < 0 || data->left < 0 || data->bottom < 0 || data->right < 0 ||
data->top >= data->bottom || data->left >= data->right) {
return false;
}
*outEndPtr = endPtr;
return true;
}
uint32_t RoiStringHelper::CountExistingRois(const char *roiStr)
{
uint32_t count = 0;
size_t len = strlen(roiStr);
for (size_t i = 0; i < len; i++) {
if (roiStr[i] == ';') {
count++;
}
}
return count + (len > 0 && roiStr[len - 1] != ';');
}
int RoiStringHelper::BuildRoiSegmentStr(char *buffer, size_t bufferSize, OH_AVFormat *avFormat)
{
int32_t top = 0;
int32_t left = 0;
int32_t bottom = 0;
int32_t right = 0;
OH_AVFormat_GetIntValue(avFormat, OH_MD_KEY_VIDEO_METADATA_ROI_TOP, &top);
OH_AVFormat_GetIntValue(avFormat, OH_MD_KEY_VIDEO_METADATA_ROI_LEFT, &left);
OH_AVFormat_GetIntValue(avFormat, OH_MD_KEY_VIDEO_METADATA_ROI_BOTTOM, &bottom);
OH_AVFormat_GetIntValue(avFormat, OH_MD_KEY_VIDEO_METADATA_ROI_RIGHT, &right);
int written = snprintf_s(buffer, bufferSize, bufferSize - 1, "%d,%d-%d,%d", top, left, bottom, right);
CHECK_AND_RETURN_RET_LOG(written >= 0, -1, "Snprintf coords failed!");
int writePos = written;
bool isFirstKv = true;
for (uint32_t i = 0; i < ROI_KV_COUNT; i++) {
int32_t val = 0;
if (!OH_AVFormat_GetIntValue(avFormat, ROI_KV_TABLE[i].key, &val)) {
continue;
}
if (isFirstKv) {
CHECK_AND_RETURN_RET_LOG(writePos < static_cast<int>(bufferSize), -1, "Buffer overflow!");
buffer[writePos++] = '=';
isFirstKv = false;
} else {
CHECK_AND_RETURN_RET_LOG(writePos < static_cast<int>(bufferSize), -1, "Buffer overflow!");
buffer[writePos++] = ',';
}
int n = snprintf_s(buffer + writePos, bufferSize - writePos, bufferSize - writePos - 1,
"%s:%d", ROI_KV_TABLE[i].keyName, val);
CHECK_AND_RETURN_RET_LOG(n >= 0, -1, "Snprintf kv pair failed!");
writePos += n;
}
CHECK_AND_RETURN_RET_LOG(writePos + 1 < static_cast<int>(bufferSize), -1, "Buffer overflow!");
buffer[writePos++] = ';';
buffer[writePos] = '\0';
return writePos;
}
OH_AVErrCode RoiStringHelper::AppendSegmentToRoiStr(char **roiStrInOut, const char *segment,
size_t oldLen, size_t extraLen)
{
size_t segLen = strlen(segment);
size_t newBufferSize = oldLen + extraLen + segLen + 1;
char *newStr = static_cast<char *>(malloc(newBufferSize));
CHECK_AND_RETURN_RET_LOG(newStr != nullptr, AV_ERR_NO_MEMORY, "Malloc roi str buffer failed!");
const char *oldStr = (*roiStrInOut != nullptr) ? *roiStrInOut : "";
if (oldLen > 0 && memcpy_s(newStr, newBufferSize, oldStr, oldLen) != EOK) {
free(newStr);
return AV_ERR_NO_MEMORY;
}
if (extraLen > 0) {
newStr[oldLen] = ';';
}
if (memcpy_s(newStr + oldLen + extraLen, segLen + 1, segment, segLen + 1) != EOK) {
free(newStr);
return AV_ERR_NO_MEMORY;
}
free(*roiStrInOut);
*roiStrInOut = newStr;
return AV_ERR_OK;
}
bool RoiStringHelper::ParseOldFormatDqp(OH_AVFormat *avFormat, const char *eqPos)
{
const char *dqpEndPtr = nullptr;
int32_t dqpVal = 0;
if (!ParseInt32(eqPos + 1, &dqpVal, &dqpEndPtr) || (*dqpEndPtr != ';' && *dqpEndPtr != '\0')) {
return false;
}
if (dqpVal < ROI_KV_TABLE[0].minVal || dqpVal > ROI_KV_TABLE[0].maxVal) {
return false;
}
OH_AVFormat_SetIntValue(avFormat, OH_MD_KEY_VIDEO_METADATA_ROI_DELTA_QP, dqpVal);
return true;
}
bool RoiStringHelper::ValidateAndParseRoiKvpairs(OH_AVFormat *avFormat, const char *eqPos, const char *segEnd)
{
const char *curPtr = eqPos + 1;
bool isCommaPending = false;
bool isKvSet[ROI_KV_COUNT] = {false};
bool isAnyKvPrefix = false;
while (*curPtr != '\0' && (segEnd == nullptr || curPtr < segEnd)) {
uint32_t matchedIdx = ROI_KV_COUNT;
for (uint32_t i = 0; i < ROI_KV_COUNT; i++) {
if (strncmp(curPtr, ROI_KV_TABLE[i].keyName, ROI_KV_TABLE[i].keyNameLen) != 0 ||
curPtr[ROI_KV_TABLE[i].keyNameLen] != ':') {
continue;
}
matchedIdx = i;
break;
}
if (matchedIdx == ROI_KV_COUNT) {
break;
}
if (isKvSet[matchedIdx]) {
return false;
}
isCommaPending = false;
isKvSet[matchedIdx] = true;
isAnyKvPrefix = true;
const char *numEndPtr = nullptr;
int32_t val = 0;
if (!ParseInt32(curPtr + ROI_KV_TABLE[matchedIdx].keyNameLen + 1, &val, &numEndPtr)) {
return false;
}
if (val < ROI_KV_TABLE[matchedIdx].minVal || val > ROI_KV_TABLE[matchedIdx].maxVal) {
return false;
}
OH_AVFormat_SetIntValue(avFormat, ROI_KV_TABLE[matchedIdx].key, val);
curPtr = numEndPtr;
if (*curPtr == ',') {
curPtr++;
isCommaPending = true;
} else if (*curPtr != ';' && *curPtr != '\0') {
return false;
}
}
if (isAnyKvPrefix) {
return !isCommaPending && (*curPtr == ';' || *curPtr == '\0');
}
return ParseOldFormatDqp(avFormat, eqPos);
}
bool RoiStringHelper::IsSameRoiFormat(OH_AVFormat *a, OH_AVFormat *b)
{
int32_t aTop = 0;
int32_t aLeft = 0;
int32_t aBottom = 0;
int32_t aRight = 0;
int32_t bTop = 0;
int32_t bLeft = 0;
int32_t bBottom = 0;
int32_t bRight = 0;
OH_AVFormat_GetIntValue(a, OH_MD_KEY_VIDEO_METADATA_ROI_TOP, &aTop);
OH_AVFormat_GetIntValue(a, OH_MD_KEY_VIDEO_METADATA_ROI_LEFT, &aLeft);
OH_AVFormat_GetIntValue(a, OH_MD_KEY_VIDEO_METADATA_ROI_BOTTOM, &aBottom);
OH_AVFormat_GetIntValue(a, OH_MD_KEY_VIDEO_METADATA_ROI_RIGHT, &aRight);
OH_AVFormat_GetIntValue(b, OH_MD_KEY_VIDEO_METADATA_ROI_TOP, &bTop);
OH_AVFormat_GetIntValue(b, OH_MD_KEY_VIDEO_METADATA_ROI_LEFT, &bLeft);
OH_AVFormat_GetIntValue(b, OH_MD_KEY_VIDEO_METADATA_ROI_BOTTOM, &bBottom);
OH_AVFormat_GetIntValue(b, OH_MD_KEY_VIDEO_METADATA_ROI_RIGHT, &bRight);
if (aTop != bTop || aLeft != bLeft || aBottom != bBottom || aRight != bRight) {
return false;
}
for (uint32_t i = 0; i < ROI_KV_COUNT; i++) {
int32_t aVal = 0;
int32_t bVal = 0;
bool aHas = OH_AVFormat_GetIntValue(a, ROI_KV_TABLE[i].key, &aVal);
bool bHas = OH_AVFormat_GetIntValue(b, ROI_KV_TABLE[i].key, &bVal);
if (aHas != bHas || (aHas && aVal != bVal)) {
return false;
}
}
return true;
}
uint32_t RoiStringHelper::DedupFormats(OH_AVFormat **formats, uint32_t count)
{
uint32_t uniqueNum = 0;
for (uint32_t i = 0; i < count; i++) {
bool isDuplicate = false;
for (uint32_t j = 0; j < uniqueNum; j++) {
if (IsSameRoiFormat(formats[i], formats[j])) {
isDuplicate = true;
break;
}
}
if (isDuplicate) {
OH_AVFormat_Destroy(formats[i]);
} else {
formats[uniqueNum++] = formats[i];
}
}
return uniqueNum;
}
OH_AVFormat *RoiStringHelper::ParseOneRoiSegmentToFormat(const char *start, const char *segEnd)
{
RoiRectangleData data = {};
const char *endPtr = nullptr;
if (!ParseRectangleSegment(start, &data, &endPtr)) {
return nullptr;
}
OH_AVFormat *avFormat = OH_AVFormat_Create();
CHECK_AND_RETURN_RET_LOG(avFormat != nullptr, nullptr, "Create avformat failed!");
OH_AVFormat_SetIntValue(avFormat, OH_MD_KEY_VIDEO_METADATA_ROI_TOP, data.top);
OH_AVFormat_SetIntValue(avFormat, OH_MD_KEY_VIDEO_METADATA_ROI_LEFT, data.left);
OH_AVFormat_SetIntValue(avFormat, OH_MD_KEY_VIDEO_METADATA_ROI_BOTTOM, data.bottom);
OH_AVFormat_SetIntValue(avFormat, OH_MD_KEY_VIDEO_METADATA_ROI_RIGHT, data.right);
if (*endPtr == '=' && (segEnd == nullptr || endPtr < segEnd)) {
if (!ValidateAndParseRoiKvpairs(avFormat, endPtr, segEnd)) {
OH_AVFormat_Destroy(avFormat);
return nullptr;
}
}
return avFormat;
}
OH_AVErrCode RoiStringHelper::AppendRoiString(char **roiStrInOut, OH_AVFormat *format)
{
CHECK_AND_RETURN_RET_LOG(roiStrInOut != nullptr && format != nullptr, AV_ERR_INVALID_VAL,
"roiStrInOut or format is nullptr!");
if (!ValidateRoiFormat(format)) {
return AV_ERR_INVALID_VAL;
}
char segmentBuf[ROI_SEGMENT_BUF_SIZE] = {0};
int written = BuildRoiSegmentStr(segmentBuf, ROI_SEGMENT_BUF_SIZE, format);
CHECK_AND_RETURN_RET_LOG(written >= 0, AV_ERR_NO_MEMORY, "Build roi segment str failed!");
size_t segLen = strlen(segmentBuf);
size_t oldLen = 0;
size_t extraLen = 0;
size_t newTotalLen = segLen;
if (*roiStrInOut != nullptr) {
oldLen = strlen(*roiStrInOut);
extraLen = (oldLen > 0 && (*roiStrInOut)[oldLen - 1] != ';') ? 1 : 0;
newTotalLen = oldLen + extraLen + segLen;
}
if (newTotalLen > ROI_STR_MAX_LEN) {
return AV_ERR_NO_MEMORY;
}
return AppendSegmentToRoiStr(roiStrInOut, segmentBuf, oldLen, extraLen);
}
OH_AVErrCode RoiStringHelper::GetRoiCount(const char *roiStr, uint32_t *outCount)
{
CHECK_AND_RETURN_RET_LOG(roiStr != nullptr && outCount != nullptr, AV_ERR_INVALID_VAL,
"roiStr or outCount is nullptr!");
uint32_t estimatedCount = CountExistingRois(roiStr);
if (estimatedCount == 0) {
*outCount = 0;
return AV_ERR_OK;
}
size_t allocSize = sizeof(OH_AVFormat *) * static_cast<size_t>(estimatedCount);
OH_AVFormat **tempFormats = static_cast<OH_AVFormat **>(malloc(allocSize));
CHECK_AND_RETURN_RET_LOG(tempFormats != nullptr, AV_ERR_NO_MEMORY, "Malloc temp formats failed!");
uint32_t actualCount = 0;
OH_AVErrCode ret = ParseRoiString(roiStr, tempFormats, estimatedCount, &actualCount);
if (ret != AV_ERR_OK) {
free(tempFormats);
return ret;
}
for (uint32_t i = 0; i < actualCount; i++) {
OH_AVFormat_Destroy(tempFormats[i]);
}
free(tempFormats);
*outCount = actualCount;
return AV_ERR_OK;
}
OH_AVErrCode RoiStringHelper::ParseRoiString(const char *roiStr, OH_AVFormat **outFormats,
uint32_t maxCapacity, uint32_t *outCount)
{
CHECK_AND_RETURN_RET_LOG(roiStr != nullptr && outFormats != nullptr && outCount != nullptr,
AV_ERR_INVALID_VAL, "roiStr, outFormats or outCount is nullptr!");
uint32_t estimatedCount = CountExistingRois(roiStr);
if (estimatedCount == 0) {
*outCount = 0;
return AV_ERR_OK;
}
size_t allocSize = sizeof(OH_AVFormat *) * static_cast<size_t>(estimatedCount);
OH_AVFormat **allFormats = static_cast<OH_AVFormat **>(malloc(allocSize));
CHECK_AND_RETURN_RET_LOG(allFormats != nullptr, AV_ERR_NO_MEMORY, "Malloc all formats failed!");
uint32_t parsedNum = 0;
const char *curPtr = roiStr;
while (*curPtr != '\0') {
const char *segEnd = strchr(curPtr, ';');
OH_AVFormat *avFormat = ParseOneRoiSegmentToFormat(curPtr, segEnd);
if (avFormat != nullptr) {
allFormats[parsedNum++] = avFormat;
}
if (segEnd == nullptr) {
break;
}
curPtr = segEnd + 1;
}
uint32_t uniqueNum = DedupFormats(allFormats, parsedNum);
uint32_t copyNum = (uniqueNum < maxCapacity) ? uniqueNum : maxCapacity;
for (uint32_t i = 0; i < copyNum; i++) {
outFormats[i] = allFormats[i];
}
for (uint32_t i = copyNum; i < uniqueNum; i++) {
OH_AVFormat_Destroy(allFormats[i]);
}
free(allFormats);
*outCount = copyNum;
return AV_ERR_OK;
}
OH_AVErrCode OH_VideoMetadata_AppendRoiString(char **roiStrInOut, OH_AVFormat *format)
{
CHECK_AND_RETURN_RET_LOG(roiStrInOut != nullptr, AV_ERR_INVALID_VAL, "roiStrInOut is nullptr!");
CHECK_AND_RETURN_RET_LOG(format != nullptr, AV_ERR_INVALID_VAL, "format is nullptr!");
OH_AVErrCode ret = RoiStringHelper::AppendRoiString(roiStrInOut, format);
CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, ret, "Append roi string failed, ret=%{public}d!", ret);
AVCODEC_LOGD("Append roi string success!");
return ret;
}
OH_AVErrCode OH_VideoMetadata_GetRoiCount(const char *roiStr, uint32_t *outCount)
{
CHECK_AND_RETURN_RET_LOG(outCount != nullptr, AV_ERR_INVALID_VAL, "outCount is nullptr!");
*outCount = 0;
CHECK_AND_RETURN_RET_LOG(roiStr != nullptr, AV_ERR_INVALID_VAL, "roiStr is nullptr!");
OH_AVErrCode ret = RoiStringHelper::GetRoiCount(roiStr, outCount);
CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, ret, "Get roi count failed, ret=%{public}d!", ret);
AVCODEC_LOGD("Get roi count success, count=%{public}u!", *outCount);
return ret;
}
OH_AVErrCode OH_VideoMetadata_ParseRoiString(const char *roiStr, OH_AVFormat **outFormats,
uint32_t maxCapacity, uint32_t *outCount)
{
CHECK_AND_RETURN_RET_LOG(outCount != nullptr, AV_ERR_INVALID_VAL, "outCount is nullptr!");
*outCount = 0;
CHECK_AND_RETURN_RET_LOG(roiStr != nullptr, AV_ERR_INVALID_VAL, "roiStr is nullptr!");
CHECK_AND_RETURN_RET_LOG(outFormats != nullptr, AV_ERR_INVALID_VAL, "outFormats is nullptr!");
OH_AVErrCode ret = RoiStringHelper::ParseRoiString(roiStr, outFormats, maxCapacity, outCount);
CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, ret, "Parse roi string failed, ret=%{public}d!", ret);
AVCODEC_LOGD("Parse roi string success, count=%{public}u!", *outCount);
return ret;
}