mirror of
https://github.com/ittiam-systems/libhevc.git
synced 2026-08-15 15:22:36 +07:00
Some checks failed
CMake / build (cmake, aarch64-linux-gnu-gcc, -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchains/aarch64_toolchain.cmake, aarch64-linux-gnu-g++, ubuntu-latest-cross-aarch64-cmake, ubuntu-latest) (push) Has been cancelled
CMake / build (cmake, arm-linux-gnueabihf-gcc, -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchains/aarch32_toolchain.cmake, arm-linux-gnueabihf-g++, ubuntu-latest-cross-aarch32-cmake, ubuntu-latest) (push) Has been cancelled
CMake / build (cmake, clang, , clang++, macos-latest-clang-cmake, macos-latest) (push) Has been cancelled
CMake / build (cmake, clang, , clang++, ubuntu-24.04-arm-clang-cmake, ubuntu-24.04-arm) (push) Has been cancelled
CMake / build (cmake, clang, , clang++, ubuntu-latest-clang-cmake, ubuntu-latest) (push) Has been cancelled
CMake / build (cmake, clang, -DSANITIZE=fuzzer-no-link,address, clang++, ubuntu-latest-clang-cmake-asan-fuzzer, ubuntu-latest) (push) Has been cancelled
CMake / build (cmake, clang, -G Ninja, clang++, ubuntu-latest-clang-cmake-ninja, ubuntu-latest) (push) Has been cancelled
CMake / build (cmake, gcc, , g++, ubuntu-latest-gcc-cmake, ubuntu-latest) (push) Has been cancelled
Encoder tests currently do not dump recon as ihevce_plugin doesn't yet support recon dump. This will be enabled once the recon dump is supported. Decoder tests currently validate just a few files from big buck bunny. Use "ctest" run all the tests when using cmake. This downloads the required media files and uses them in the tests. If running the tests directly using the test binaries, then download the media files and export HEVC_TEST_DIR to the extracted folder before running the binaries directly.
378 lines
11 KiB
C++
378 lines
11 KiB
C++
/******************************************************************************
|
|
*
|
|
* Copyright (C) 2026 Ittiam Systems Pvt Ltd, Bangalore
|
|
*
|
|
* 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 "EncHelper.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
|
|
#include "BitsFile.h"
|
|
#include "Md5Wrapper.h"
|
|
#include "RawFile.h"
|
|
|
|
// Include internal libhevc encoder headers here inside the .cpp file to prevent
|
|
// public header pollution
|
|
// clang-format off
|
|
extern "C" {
|
|
#include "ihevc_typedefs.h"
|
|
#include "itt_video_api.h" // Defines IV_ARCH_T and its ARCH_* values
|
|
#include "ihevce_api.h"
|
|
#include "ihevce_plugin.h"
|
|
}
|
|
// clang-format on
|
|
|
|
static IV_ARCH_T getDefaultArch() { return ARCH_NA; }
|
|
|
|
// Map architecture to native enum using local mapper helper
|
|
static IV_ARCH_T getNativeArch(libhevc::test::Arch arch) {
|
|
switch (arch) {
|
|
case libhevc::test::Arch::defaultArch:
|
|
return getDefaultArch();
|
|
case libhevc::test::Arch::generic:
|
|
#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || \
|
|
defined(_M_IX86)
|
|
return ARCH_NA;
|
|
#else // defined(__arm__) || defined(_M_ARM) || defined(__aarch64__) ||
|
|
// defined(_M_ARM64)
|
|
return ARCH_ARM_NONEON;
|
|
#endif
|
|
case libhevc::test::Arch::armv7:
|
|
return ARCH_ARM_A9Q;
|
|
case libhevc::test::Arch::armv8:
|
|
return ARCH_ARM_V8_NEON;
|
|
case libhevc::test::Arch::x86_sse42:
|
|
return ARCH_X86_SSE4;
|
|
case libhevc::test::Arch::x86_avx2:
|
|
return ARCH_X86_AVX2;
|
|
}
|
|
return ARCH_NA;
|
|
}
|
|
|
|
#ifndef DISABLE_MD5
|
|
// Re-declare the internal C-struct layout of iv_enc_recon_data_buffs_t
|
|
// to avoid including private, fragile internal encoder headers.
|
|
typedef struct {
|
|
int32_t i4_size;
|
|
int32_t i4_poc;
|
|
int32_t i4_end_flag;
|
|
int32_t i4_is_last_buf;
|
|
void* pv_y_buf;
|
|
void* pv_cb_buf;
|
|
void* pv_cr_buf;
|
|
int32_t i4_y_pixels;
|
|
int32_t i4_uv_pixels;
|
|
} iv_enc_recon_data_buffs_t;
|
|
|
|
// Reconstructed frame callback function called by the encoder
|
|
extern "C" IV_API_CALL_STATUS_T reconCallback(void* pv_recon_cb_handle,
|
|
void* pv_curr_out,
|
|
WORD32 i4_bitrate_instance,
|
|
WORD32 i4_res_instance) {
|
|
(void)i4_bitrate_instance;
|
|
(void)i4_res_instance;
|
|
if (pv_recon_cb_handle && pv_curr_out) {
|
|
EncHelper* helper = static_cast<EncHelper*>(pv_recon_cb_handle);
|
|
std::string md5 = helper->computeReconFrameMd5(pv_curr_out);
|
|
helper->addReconMd5(md5);
|
|
}
|
|
return IV_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
std::unique_ptr<EncHelper> EncHelper::Builder::build() {
|
|
std::unique_ptr<EncHelper> helper(new EncHelper());
|
|
helper->mFormat = mFormat;
|
|
helper->mCores = mCores;
|
|
helper->mArch = mArch;
|
|
helper->mWidth = mWidth;
|
|
helper->mHeight = mHeight;
|
|
helper->mBitrate = mBitrate;
|
|
helper->mFrameRateNum = mFrameRateNum;
|
|
helper->mFrameRateDenom = mFrameRateDenom;
|
|
helper->mRcMode = mRcMode;
|
|
helper->mFrameQp = mFrameQp;
|
|
helper->mQualityPreset = mQualityPreset;
|
|
helper->mMaxClosedGop = mMaxClosedGop;
|
|
helper->mMinClosedGop = mMinClosedGop;
|
|
helper->mMaxCraOpenGop = mMaxCraOpenGop;
|
|
helper->mProfile = mProfile;
|
|
helper->mTier = mTier;
|
|
helper->mInputFilePath = mInputFilePath;
|
|
helper->mOutFilePath = mOutFilePath;
|
|
helper->mReconMd5FilePath = mReconMd5FilePath;
|
|
|
|
// Open input raw YUV file
|
|
if (!mInputFilePath.has_value() ||
|
|
!helper->mRawFile.open(*mInputFilePath, true)) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Open output bitstream file
|
|
if (!mOutFilePath.has_value() ||
|
|
!helper->mBitsFile.open(*mOutFilePath, false)) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Initialize native encoder
|
|
if (!helper->initEncoder()) {
|
|
return nullptr;
|
|
}
|
|
return helper;
|
|
}
|
|
|
|
bool EncHelper::initEncoder() {
|
|
ihevce_static_cfg_params_t params = {};
|
|
|
|
// Get default configuration parameters from encoder library
|
|
if (IHEVCE_EFAIL == ihevce_set_def_params(¶ms)) {
|
|
return false;
|
|
}
|
|
|
|
// Overwrite default configuration with builder properties
|
|
params.s_src_prms.i4_width = mWidth;
|
|
params.s_src_prms.i4_height = mHeight;
|
|
params.s_multi_thrd_prms.i4_max_num_cores = mCores;
|
|
|
|
// Map architecture to native enum using local mapper helper
|
|
params.e_arch_type = getNativeArch(mArch);
|
|
|
|
// Map chroma format to native enum
|
|
IV_COLOR_FORMAT_T colorFmt = IV_YUV_420P;
|
|
switch (mFormat) {
|
|
case libhevc::test::Format::yuv400p:
|
|
colorFmt = IV_GRAY;
|
|
break;
|
|
case libhevc::test::Format::yuv420p:
|
|
colorFmt = IV_YUV_420P;
|
|
break;
|
|
case libhevc::test::Format::yuv420sp_uv:
|
|
colorFmt = IV_YUV_420SP_UV;
|
|
break;
|
|
case libhevc::test::Format::yuv420sp_vu:
|
|
colorFmt = IV_YUV_420SP_VU;
|
|
break;
|
|
case libhevc::test::Format::yuv422p:
|
|
colorFmt = IV_YUV_422P;
|
|
break;
|
|
case libhevc::test::Format::yuv444p:
|
|
colorFmt = IV_YUV_444P;
|
|
break;
|
|
}
|
|
params.s_src_prms.inp_chr_format = colorFmt;
|
|
|
|
// Set bitrates (Target & Peak)
|
|
params.s_tgt_lyr_prms.as_tgt_params[0].ai4_tgt_bitrate[0] = mBitrate;
|
|
params.s_tgt_lyr_prms.as_tgt_params[0].ai4_peak_bitrate[0] = mBitrate;
|
|
|
|
// Set frame rate
|
|
params.s_src_prms.i4_frm_rate_num = mFrameRateNum;
|
|
params.s_src_prms.i4_frm_rate_denom = mFrameRateDenom;
|
|
|
|
// Set rate control mode, QP, and quality preset
|
|
params.s_config_prms.i4_rate_control_mode = mRcMode;
|
|
params.s_tgt_lyr_prms.as_tgt_params[0].ai4_frame_qp[0] = mFrameQp;
|
|
params.s_tgt_lyr_prms.as_tgt_params[0].i4_quality_preset =
|
|
static_cast<IHEVCE_QUALITY_CONFIG_T>(mQualityPreset);
|
|
|
|
// Set GOP parameters
|
|
params.s_coding_tools_prms.i4_max_closed_gop_period = mMaxClosedGop;
|
|
params.s_coding_tools_prms.i4_min_closed_gop_period = mMinClosedGop;
|
|
params.s_coding_tools_prms.i4_max_cra_open_gop_period = mMaxCraOpenGop;
|
|
|
|
// Set Profile & Tier
|
|
params.s_out_strm_prms.i4_codec_profile = mProfile;
|
|
params.s_out_strm_prms.i4_codec_tier = mTier;
|
|
|
|
// Configure reconstruction frame dumping if requested
|
|
if (mReconMd5FilePath.has_value()) {
|
|
params.i4_save_recon = 1;
|
|
} else {
|
|
params.i4_save_recon = 0;
|
|
}
|
|
// TODO: Remove the following once recon save is verified
|
|
params.i4_save_recon = 0;
|
|
|
|
// Call native init
|
|
if (ihevce_init(¶ms, &mCodec) != IHEVCE_EOK) {
|
|
mCodec = nullptr;
|
|
return false;
|
|
}
|
|
|
|
if (!mInputBuf.allocBuffer(mWidth, mHeight, mFormat)) {
|
|
deinitEncoder();
|
|
return false;
|
|
}
|
|
|
|
if (!mOutputBuf.allocBuffer(mWidth * mHeight * 3 >> 1)) {
|
|
deinitEncoder();
|
|
return false;
|
|
}
|
|
|
|
mReconMd5s.clear();
|
|
return true;
|
|
}
|
|
|
|
void EncHelper::deinitEncoder() {
|
|
if (mCodec) {
|
|
ihevce_close(mCodec);
|
|
mCodec = nullptr;
|
|
}
|
|
}
|
|
|
|
void EncHelper::addReconMd5(const std::string& md5) {
|
|
std::lock_guard<std::mutex> lock(mReconMutex);
|
|
mReconMd5s.push_back(md5);
|
|
}
|
|
|
|
std::string EncHelper::computeReconFrameMd5(void* pv_curr_out) {
|
|
iv_enc_recon_data_buffs_t* reconBuf =
|
|
static_cast<iv_enc_recon_data_buffs_t*>(pv_curr_out);
|
|
Md5Wrapper md5;
|
|
md5.init();
|
|
|
|
// Y plane (contiguous, size = mWidth * mHeight)
|
|
if (reconBuf->pv_y_buf) {
|
|
md5.update(static_cast<const uint8_t*>(reconBuf->pv_y_buf),
|
|
mWidth * mHeight);
|
|
}
|
|
|
|
// Chroma planes (U and V packed contiguously in pv_cb_buf)
|
|
if (mFormat != libhevc::test::Format::yuv400p && reconBuf->pv_cb_buf) {
|
|
size_t uvWidth =
|
|
(mFormat == libhevc::test::Format::yuv444p) ? mWidth : mWidth / 2;
|
|
size_t uvHeight = (mFormat == libhevc::test::Format::yuv444p ||
|
|
mFormat == libhevc::test::Format::yuv422p)
|
|
? mHeight
|
|
: mHeight / 2;
|
|
size_t chromaSize = uvWidth * uvHeight;
|
|
md5.update(static_cast<const uint8_t*>(reconBuf->pv_cb_buf),
|
|
chromaSize * 2);
|
|
}
|
|
|
|
return md5.finalize();
|
|
}
|
|
|
|
bool EncHelper::encodeFrame(bool& bytesProduced, bool noInput) {
|
|
bytesProduced = false;
|
|
if (!mCodec) {
|
|
return false;
|
|
}
|
|
|
|
ihevce_inp_buf_t inpPic = {};
|
|
ihevce_out_buf_t outPic = {};
|
|
|
|
ihevce_inp_buf_t* pInpPic = nullptr;
|
|
if (!noInput) {
|
|
// Map RawBuf planes, strides, and sizes directly into inpPic
|
|
size_t planesCount = mInputBuf.numPlanes();
|
|
for (size_t i = 0; i < planesCount; ++i) {
|
|
inpPic.apv_inp_planes[i] = mInputBuf.planeData(i);
|
|
inpPic.ai4_inp_strd[i] = mInputBuf.stride(i);
|
|
inpPic.ai4_inp_size[i] = mInputBuf.stride(i) * mInputBuf.planeHeight(i);
|
|
}
|
|
|
|
inpPic.i4_curr_bitrate = mBitrate;
|
|
inpPic.i4_curr_peak_bitrate = mBitrate;
|
|
inpPic.u8_pts = 0;
|
|
inpPic.i4_force_idr_flag = 0;
|
|
pInpPic = &inpPic;
|
|
}
|
|
|
|
IHEVCE_PLUGIN_STATUS_T status = ihevce_encode(mCodec, pInpPic, &outPic);
|
|
if (status != IHEVCE_EOK) {
|
|
return false;
|
|
}
|
|
|
|
if (outPic.i4_bytes_generated > 0) {
|
|
std::memcpy(mOutputBuf.data(), outPic.pu1_output_buf,
|
|
outPic.i4_bytes_generated);
|
|
mOutputBuf.setSize(outPic.i4_bytes_generated);
|
|
mOutputBuf.setOffset(0);
|
|
bytesProduced = true;
|
|
|
|
// Write output bitstream chunk directly to file
|
|
mBitsFile.write(mOutputBuf, outPic.i4_bytes_generated);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EncHelper::flushEncoder() {
|
|
if (!mCodec) {
|
|
return false;
|
|
}
|
|
|
|
// Loop internally and delegate to encodeFrame with noInput = true
|
|
while (true) {
|
|
bool bytesProduced = false;
|
|
if (!encodeFrame(bytesProduced, true)) {
|
|
return false;
|
|
}
|
|
if (!bytesProduced) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EncHelper::encodeFile() {
|
|
mReconMd5s.clear();
|
|
|
|
// Encode SPS/PPS header first
|
|
ihevce_out_buf_t outHdr = {};
|
|
if (IHEVCE_EOK == ihevce_encode_header(mCodec, &outHdr)) {
|
|
if (outHdr.i4_bytes_generated > 0) {
|
|
mOutputBuf.setSize(outHdr.i4_bytes_generated);
|
|
mOutputBuf.setOffset(0);
|
|
std::memcpy(mOutputBuf.data(), outHdr.pu1_output_buf,
|
|
outHdr.i4_bytes_generated);
|
|
mBitsFile.write(mOutputBuf, outHdr.i4_bytes_generated);
|
|
}
|
|
}
|
|
|
|
// Main frame encoding loop
|
|
while (mRawFile.read(mInputBuf)) {
|
|
bool bytesProduced = false;
|
|
if (!encodeFrame(bytesProduced)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Flush encoder
|
|
if (!flushEncoder()) {
|
|
return false;
|
|
}
|
|
|
|
// Write all computed reconstruction MD5s to the output path if provided
|
|
if (mReconMd5FilePath.has_value()) {
|
|
std::ofstream md5File(*mReconMd5FilePath);
|
|
if (!md5File.is_open()) {
|
|
return false;
|
|
}
|
|
std::lock_guard<std::mutex> lock(mReconMutex);
|
|
for (const auto& md5 : mReconMd5s) {
|
|
md5File << md5 << " 0" << "\n";
|
|
}
|
|
mReconMd5s.clear();
|
|
}
|
|
|
|
return true;
|
|
}
|