mirror of
https://github.com/ittiam-systems/libhevc.git
synced 2026-08-15 23:32:35 +07:00
379 lines
11 KiB
C++
379 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;
|
||
|
|
}
|