p26001-store-acs-soc-fw-app/video/hisi/mpp5/VideoEncoder.cpp
2026-09-28 21:33:04 +08:00

682 lines
22 KiB
C++

/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "VideoEncoder.h"
#include "ResourceManager.h"
#include "Logger.h"
#include "util/Math.h"
#include <errno.h>
using namespace Util;
/*
*******************************************************************************
* CONSTANTS
*******************************************************************************
*/
#define MIN_ALIGN 2
/*
*******************************************************************************
* MACROS
*******************************************************************************
*/
#define __set_basic(attr, gop, sfr, dfr) \
attr.gop = (gop); \
attr.src_frame_rate = (sfr); \
attr.dst_frame_rate = (dfr);
#define __set_cbr(attr, gop, st, sfr, dfr, br) \
__set_basic(attr, gop, sfr, dfr) attr.stats_time = (st); \
attr.bit_rate = (br);
#define __set_vbr(attr, gop, st, sfr, dfr, br) \
__set_basic(attr, gop, sfr, dfr) attr.stats_time = (st); \
attr.max_bit_rate = (br);
#define __set_avbr __set_vbr
#define __set_qvbr(attr, gop, st, sfr, dfr, br) \
__set_basic(attr, gop, sfr, dfr) attr.stats_time = (st); \
attr.target_bit_rate = (br);
namespace Video
{
namespace MPP5
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
VideoEncoder::Preprocessor::Preprocessor(const std::string& name, uint32_t id)
: MPPChannel((uint32_t)HI_ID_VPSS, id, 0), _name(Poco::format("%s(VPSS#%u)", name, id))
{
}
VideoEncoder::Preprocessor::~Preprocessor()
{
}
void VideoEncoder::Preprocessor::start(uint32_t iwidth, uint32_t iheight, uint32_t owidth, uint32_t oheight)
{
hi_vpss_grp_attr attr = {};
attr.nr_en = HI_FALSE;
attr.ie_en = HI_FALSE;
attr.dci_en = HI_FALSE;
attr.buf_share_en = HI_FALSE;
attr.buf_share_chn = 0;
attr.mcf_en = HI_FALSE;
attr.max_width = iwidth;
attr.max_height = iheight;
attr.max_dei_width = iwidth;
attr.max_dei_height = iheight;
attr.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_420;
attr.dynamic_range = HI_DYNAMIC_RANGE_SDR8;
attr.frame_rate.src_frame_rate = -1;
attr.frame_rate.dst_frame_rate = -1;
attr.dei_mode = HI_VPSS_DEI_MODE_OFF;
int rc = hi_mpi_vpss_create_grp(groupID(), &attr);
if (rc != HI_SUCCESS)
throw Poco::InvalidAccessException(Poco::format("%s: Create group FAIL: %d(%#x)", name(), rc, (uint32_t)rc),
rc);
hi_mpi_vpss_reset_grp(groupID());
rc = hi_mpi_vpss_start_grp(groupID());
if (rc != HI_SUCCESS)
throw Poco::InvalidAccessException(Poco::format("%s: Start group FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc);
hi_vpss_chn_attr chnAttr = {};
chnAttr.chn_mode = HI_VPSS_CHN_MODE_USER;
chnAttr.width = owidth;
chnAttr.height = oheight;
chnAttr.video_format = HI_VIDEO_FORMAT_LINEAR;
chnAttr.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_420;
chnAttr.dynamic_range = HI_DYNAMIC_RANGE_SDR8;
chnAttr.compress_mode = HI_COMPRESS_MODE_NONE;
chnAttr.frame_rate.src_frame_rate = -1;
chnAttr.frame_rate.dst_frame_rate = -1;
chnAttr.mirror_en = HI_FALSE;
chnAttr.flip_en = HI_FALSE;
chnAttr.depth = 0;
chnAttr.aspect_ratio.mode = HI_ASPECT_RATIO_NONE;
rc = hi_mpi_vpss_set_chn_attr(groupID(), channelID(), &chnAttr);
if (rc != HI_SUCCESS)
throw Poco::InvalidAccessException(
Poco::format("%s: Set channel#%u attr FAIL: %d(%#x)", name(), channelID(), rc, (uint32_t)rc), rc);
rc = hi_mpi_vpss_enable_chn(groupID(), channelID());
if (rc != HI_SUCCESS)
throw Poco::InvalidAccessException(
Poco::format("%s: Enable channel#%u FAIL: %d(%#x)", name(), channelID(), rc, (uint32_t)rc), rc);
}
void VideoEncoder::Preprocessor::stop()
{
int rc = hi_mpi_vpss_disable_chn(groupID(), channelID());
if (rc != HI_SUCCESS)
logger().error("%s: Disable channel#%u FAIL: %d(%#x)", name(), channelID(), rc, (uint32_t)rc);
rc = hi_mpi_vpss_stop_grp(groupID());
if (rc != HI_SUCCESS)
logger().error("%s: Stop group FAIL: %d(%#x)", name(), rc, (uint32_t)rc);
rc = hi_mpi_vpss_destroy_grp(groupID());
if (rc != HI_SUCCESS)
logger().error("%s: Destroy group FAIL: %d(%#x)", name(), rc, (uint32_t)rc);
}
uint32_t VideoEncoder::Preprocessor::groupID() const
{
return deviceID();
}
const std::string& VideoEncoder::Preprocessor::name() const
{
return _name;
}
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
Core::CodecCapabilityManager VideoEncoder::_capabilityManager(1920 * 1080 * 30 * 4 + 720 * 480 * 30 * 4);
VideoEncoder::VideoEncoder(Core::CodecID codecID)
: MPPChannel((uint32_t)HI_ID_VENC, 0, ResourceManager::allocVENCChannelID()),
Core::VideoEncoder(Poco::format("MPP5::VENC#%u(%s)", channelID(), Core::codecIDToString(codecID)), codecID),
_handle(Core::CodecCapabilityManager::INVALID_HANDLE), _preprocessor(name(), ResourceManager::allocVPSSGroupID())
{
registerNotificationHandle<Core::ParameterChangedNotification>(*this,
&VideoEncoder::handlePreviousParameterChanged);
}
VideoEncoder::~VideoEncoder()
{
ResourceManager::freeVPSSGroupID(_preprocessor.groupID());
ResourceManager::freeVENCChannelID(channelID());
releaseCapabilityHandle();
}
void VideoEncoder::start()
{
if (previousComponents().size() == 0)
throw Poco::BugcheckException(Poco::format("%s: No previous component", name()));
auto pPrevious = previousComponents().first().cast<VideoComponent>();
if (!pPrevious)
throw Poco::BugcheckException(Poco::format("%s: First previous component '%s' convert to 'VideoComponent' FAIL",
name(), previousComponents().first()->name()));
uint32_t width;
uint32_t height;
getEncodeResolution(pPrevious, width, height);
_preprocessor.start(pPrevious->getWidth(), pPrevious->getHeight(), width, height);
hi_venc_chn_attr attr = {};
attr.venc_attr.max_pic_width = width;
attr.venc_attr.max_pic_height = height;
attr.venc_attr.pic_width = width;
attr.venc_attr.pic_height = height;
attr.venc_attr.buf_size =
Util::Math::alignup(attr.venc_attr.max_pic_width * attr.venc_attr.max_pic_height * 3 / 2, 64);
attr.venc_attr.is_by_frame = HI_TRUE;
switch (getCodecID())
{
default:
case Core::CodecID::H264:
attr.venc_attr.type = HI_PT_H264;
switch (getCodecProfile())
{
case Core::CodecProfile::H264_BASELINE:
attr.venc_attr.profile = 0;
break;
default:
case Core::CodecProfile::H264_MAIN:
attr.venc_attr.profile = 1;
break;
case Core::CodecProfile::H264_HIGH:
attr.venc_attr.profile = 2;
break;
case Core::CodecProfile::H264_SVC_T:
attr.venc_attr.profile = 3;
break;
}
attr.venc_attr.h264_attr.rcn_ref_share_buf_en = HI_FALSE;
attr.venc_attr.h264_attr.frame_buf_ratio = 100;
break;
case Core::CodecID::H265:
attr.venc_attr.type = HI_PT_H265;
switch (getCodecProfile())
{
default:
case Core::CodecProfile::H265_MAIN:
attr.venc_attr.profile = 0;
break;
case Core::CodecProfile::H265_MAIN_10:
attr.venc_attr.profile = 1;
break;
}
attr.venc_attr.h265_attr.rcn_ref_share_buf_en = HI_FALSE;
attr.venc_attr.h265_attr.frame_buf_ratio = 100;
}
setChnAttrRateControl(attr, getCodecID(), getBitrateControl(), getSuitableGOP(), getSuitableSFR(), getSuitableDFR(),
getSuitableBitrate());
setChnAttrGOPMode(attr, getGOPMode(), getSuitableGOP());
int rc = hi_mpi_venc_create_chn(channelID(), &attr);
if (rc != HI_SUCCESS)
throw Poco::InvalidAccessException(Poco::format("%s: Create channel FAIL: %#x", name(), (uint32_t)rc), rc);
rc = hi_mpi_venc_reset_chn(channelID());
if (rc != HI_SUCCESS)
throw Poco::InvalidAccessException(Poco::format("%s: Reset channel recv FAIL: %#x", name(), (uint32_t)rc), rc);
hi_venc_start_param param;
param.recv_pic_num = -1;
rc = hi_mpi_venc_start_chn(channelID(), &param);
if (rc != HI_SUCCESS)
throw Poco::InvalidAccessException(Poco::format("%s: Start channel recv FAIL: %#x", name(), (uint32_t)rc), rc);
task().run(
[this](std::atomic<bool>& isCancelled) -> void
{
while (!isCancelled)
{
try
{
postToNextComponents(sharedFromThis(), readPacket(1000), Core::MediaType::VIDEO);
}
catch (Poco::Exception& e)
{
logger().error("%s: %s", name(), e.displayText());
Poco::Thread::sleep(1000);
}
}
});
}
void VideoEncoder::stop()
{
task().cancel();
hi_mpi_venc_stop_chn(channelID());
hi_mpi_venc_reset_chn(channelID());
hi_mpi_venc_destroy_chn(channelID());
_preprocessor.stop();
}
void VideoEncoder::initialize()
{
uint32_t width;
uint32_t height;
getEncodeResolution(getPrevious(), width, height);
setWidth(width);
setHeight(height);
}
void VideoEncoder::deinitialize()
{
releaseCapabilityHandle();
}
void VideoEncoder::onConfigChanged(ParameterNames& names)
{
if (!started())
return;
if (names.exists(_CodecProfile) || names.exists(_Width) || names.exists(_Height) || names.exists(_FrameRate))
{
serialCodeManager().put(_Width, getWidth());
serialCodeManager().put(_Height, getHeight());
stop();
deinitialize();
initialize();
start();
return;
}
hi_venc_chn_attr attr;
hi_mpi_venc_get_chn_attr(channelID(), &attr);
if (names.exists(_GOPMode) || names.exists(_GOP) || names.exists(_BitrateControl) || names.exists(_Bitrate))
{
setChnAttrGOPMode(attr, getGOPMode(), getGOP());
setChnAttrRateControl(attr, getCodecID(), getBitrateControl(), getSuitableGOP(), getSuitableSFR(),
getSuitableDFR(), getSuitableBitrate());
}
hi_mpi_venc_set_chn_attr(channelID(), &attr);
}
void VideoEncoder::onLink(Core::Component::Ptr pPrevious)
{
_preprocessor.doLink(pPrevious);
_preprocessor.bind(*this);
}
void VideoEncoder::onUnlink(Core::Component::Ptr pPrevious)
{
_preprocessor.doUnlink(pPrevious);
_preprocessor.unbind(*this);
}
void VideoEncoder::handlePreviousParameterChanged(const Poco::AutoPtr<Core::ParameterChangedNotification>& pNotication)
{
if (!started())
return;
const auto& parameters = pNotication->parameters();
auto pPrevious = getPrevious();
if (parameters.exists(_Width) || parameters.exists(_Height))
{
uint32_t width;
uint32_t height;
getEncodeResolution(pPrevious, width, height);
if (getWidth() == width && getHeight() == height)
{
_preprocessor.stop();
_preprocessor.start(pPrevious->getWidth(), pPrevious->getHeight(), width, height);
}
else
{
setWidth(width);
setHeight(height);
commit();
}
}
if (parameters.exists(_FrameRate))
{
hi_venc_chn_attr attr;
hi_mpi_venc_get_chn_attr(channelID(), &attr);
setChnAttrRateControl(attr, getCodecID(), getBitrateControl(), getSuitableGOP(), getSuitableSFR(),
getSuitableDFR(), getSuitableBitrate());
hi_mpi_venc_set_chn_attr(channelID(), &attr);
}
}
Core::VideoComponent::Ptr VideoEncoder::getPrevious()
{
auto pPrevious = previousComponents().first().cast<VideoComponent>();
if (!pPrevious)
throw Poco::BugcheckException(Poco::format("%s: First previous component '%s' convert to 'VideoComponent' FAIL",
name(), previousComponents().first()->name()));
return pPrevious;
}
int VideoEncoder::getSuitableGOP()
{
return Math::clamp(getGOP(), 1, 65535);
}
int VideoEncoder::getSuitableSFR()
{
return Math::clamp(getPrevious()->getFrameRate(), HI_VENC_MIN_INPUT_FRAME_RATE, HI_VENC_MAX_INPUT_FRAME_RATE);
}
int VideoEncoder::getSuitableDFR()
{
return Math::clamp(Math::min(getFrameRate(), getSuitableSFR()), HI_VENC_MIN_INPUT_FRAME_RATE,
HI_VENC_MAX_INPUT_FRAME_RATE);
;
}
uint32_t VideoEncoder::getSuitableBitrate()
{
return Math::clamp(getBitrate() / 1000, (int64_t)HI_VENC_MIN_BITRATE, (int64_t)HI_VENC_MAX_BITRATE);
}
void VideoEncoder::getEncodeResolution(Core::VideoComponent::Ptr pPrevious, uint32_t& width, uint32_t& height)
{
if (getAdaptable() && !pPrevious.isNull())
{
width = pPrevious->getWidth();
height = pPrevious->getHeight();
}
else
{
width = getWidth();
height = getHeight();
}
width = Math::alignup(Math::clamp(width, (uint32_t)HI_VENC_H264_MIN_WIDTH, (uint32_t)HI_VENC_H264_MAX_WIDTH),
MIN_ALIGN);
height = Math::alignup(Math::clamp(height, (uint32_t)HI_VENC_H264_MIN_HEIGHT, (uint32_t)HI_VENC_H264_MAX_HEIGHT),
MIN_ALIGN);
if (width == 0 || height == 0)
{
width = 1920;
height = 1080;
}
}
Core::Packet::Ptr VideoEncoder::readPacket(uint32_t timeout)
{
static const int PACKET_DATA_SIZE_MAX = (1024 * 1024 * 2);
Core::Packet::Ptr pPacket = nullptr;
const long tvsec = timeout / 1000;
const long tvusec = 0;
int fd = hi_mpi_venc_get_fd(channelID());
while (true)
{
struct timeval tm = {
.tv_sec = tvsec,
.tv_usec = tvusec,
};
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(fd, &read_fds);
int rc = select(fd + 1, &read_fds, NULL, NULL, &tm);
if (rc < 0)
throw Poco::InvalidAccessException(
Poco::format("%s: Read stream FAIL: %s", name(), std::string(strerror(rc))), rc);
else if (0 == rc)
throw Poco::TimeoutException(Poco::format("%s: Read encoder stream timeout!", name()), rc);
if (!FD_ISSET(fd, &read_fds))
continue;
hi_venc_chn_status status;
hi_mpi_venc_query_status(channelID(), &status);
hi_venc_stream stream;
stream.pack = (hi_venc_pack*)malloc(sizeof(hi_venc_pack) * status.cur_packs);
if (!stream.pack)
continue;
stream.pack_cnt = status.cur_packs;
rc = hi_mpi_venc_get_stream(channelID(), &stream, timeout);
if (rc != HI_SUCCESS)
{
free(stream.pack);
throw Poco::InvalidAccessException(Poco::format("%s: Get encoder stream FAIL: %#x", name(), (uint32_t)rc),
rc);
}
bool done = false;
uint8_t* pBuffer = new uint8_t[PACKET_DATA_SIZE_MAX];
if (!pBuffer)
{
free(stream.pack);
throw Poco::OutOfMemoryException(Poco::format("%s: Alloc memory for encoded data: FAIL", name()), 0);
}
uint32_t length = 0;
uint32_t flags = Core::Packet::FLAG_IS_VIDEO;
for (uint32_t i = 0; i < stream.pack_cnt; i++)
{
hi_venc_pack* pack = &stream.pack[i];
uint32_t chucksize = pack->len - pack->offset;
if (chucksize < PACKET_DATA_SIZE_MAX - length)
{
memcpy(&pBuffer[length], pack->addr + pack->offset, chucksize);
length += chucksize;
}
else
logger().error("%s: Buffer size(%d) is insufficient to hold the encoded data!", PACKET_DATA_SIZE_MAX);
if (pack->is_frame_end)
{
switch (getCodecID())
{
default:
case Core::CodecID::H264:
if (stream.h264_info.ref_type == HI_VENC_BASE_IDR_SLICE)
flags |= Core::Packet::FLAG_IS_KEY;
break;
case Core::CodecID::H265:
if (stream.h265_info.ref_type == HI_VENC_BASE_IDR_SLICE)
flags |= Core::Packet::FLAG_IS_KEY;
break;
}
pPacket = new Core::Packet(serialCodeManager().code(), flags, pBuffer, length, pack->pts, pack->pts);
done = true;
break;
}
}
rc = hi_mpi_venc_release_stream(channelID(), &stream);
free(stream.pack);
stream.pack = NULL;
if (done)
break;
}
return pPacket;
}
void VideoEncoder::setChnAttrGOPMode(hi_venc_chn_attr& attr, const Core::GOPMode mode, int gop)
{
switch (mode)
{
case Core::GOPMode::NORMAL_P:
attr.gop_attr.gop_mode = HI_VENC_GOP_MODE_NORMAL_P;
attr.gop_attr.normal_p.ip_qp_delta = 2;
break;
case Core::GOPMode::SMART_P:
attr.gop_attr.gop_mode = HI_VENC_GOP_MODE_SMART_P;
attr.gop_attr.smart_p.bg_interval = gop;
attr.gop_attr.smart_p.bg_qp_delta = 4;
attr.gop_attr.smart_p.vi_qp_delta = 2;
break;
default:
throw Poco::InvalidArgumentException(
Poco::format("%s: Invalid gop-mode: %s", name(), Core::gopModeToString(getGOPMode())), -1);
}
}
void VideoEncoder::setChnAttrRateControl(hi_venc_chn_attr& attr, Core::CodecID codec, Core::BitrateControl bc, int gop,
int sfr, int dfr, uint32_t bitrate)
{
logger().debug("%s: confiure: %s, %s, GOP:%d, sfr:%d, dfr:%d, %uKbps", name(), Core::codecIDToString(codec),
Core::bitrateControlToString(bc), gop, sfr, dfr, bitrate);
if (Core::CodecID::H264 == codec)
{
switch (bc)
{
case Core::BitrateControl::CBR:
attr.rc_attr.rc_mode = HI_VENC_RC_MODE_H264_CBR;
__set_cbr(attr.rc_attr.h264_cbr, gop, 1, sfr, dfr, bitrate);
break;
case Core::BitrateControl::VBR:
attr.rc_attr.rc_mode = HI_VENC_RC_MODE_H264_VBR;
__set_vbr(attr.rc_attr.h264_vbr, gop, 1, sfr, dfr, bitrate);
break;
case Core::BitrateControl::AVBR:
attr.rc_attr.rc_mode = HI_VENC_RC_MODE_H264_AVBR;
__set_avbr(attr.rc_attr.h264_avbr, gop, 1, sfr, dfr, bitrate);
break;
case Core::BitrateControl::QVBR:
attr.rc_attr.rc_mode = HI_VENC_RC_MODE_H264_QVBR;
__set_qvbr(attr.rc_attr.h264_qvbr, gop, 1, sfr, dfr, bitrate);
break;
case Core::BitrateControl::CVBR:
case Core::BitrateControl::FIXQP:
case Core::BitrateControl::QPMAP:
default:
throw Poco::NotImplementedException(Poco::format("%s: Not implemented H.264 bitrate-control: %s", name(),
Core::bitrateControlToString(getBitrateControl())),
-1);
}
}
else if (Core::CodecID::H265 == codec)
{
switch (bc)
{
case Core::BitrateControl::CBR:
attr.rc_attr.rc_mode = HI_VENC_RC_MODE_H265_CBR;
__set_cbr(attr.rc_attr.h265_cbr, gop, 1, sfr, dfr, bitrate);
break;
case Core::BitrateControl::VBR:
attr.rc_attr.rc_mode = HI_VENC_RC_MODE_H265_VBR;
__set_vbr(attr.rc_attr.h265_vbr, gop, 1, sfr, dfr, bitrate);
break;
case Core::BitrateControl::AVBR:
attr.rc_attr.rc_mode = HI_VENC_RC_MODE_H265_AVBR;
__set_avbr(attr.rc_attr.h265_avbr, gop, 1, sfr, dfr, bitrate);
break;
case Core::BitrateControl::QVBR:
attr.rc_attr.rc_mode = HI_VENC_RC_MODE_H265_QVBR;
__set_qvbr(attr.rc_attr.h265_qvbr, gop, 1, sfr, dfr, bitrate);
break;
case Core::BitrateControl::CVBR:
case Core::BitrateControl::FIXQP:
case Core::BitrateControl::QPMAP:
default:
throw Poco::NotImplementedException(Poco::format("%s: Not implemented H.265 bitrate-control: %s", name(),
Core::bitrateControlToString(getBitrateControl())),
-1);
}
}
}
void VideoEncoder::releaseCapabilityHandle()
{
}
void VideoEncoder::initModule()
{
hi_venc_mod_param param = {};
param.mod_type = HI_VENC_MOD_H264;
param.h264_mod_param.one_stream_buf = 0;
param.h264_mod_param.mini_buf_mode = 0;
param.h264_mod_param.low_power_mode = 0;
param.h264_mod_param.vb_src = HI_VB_SRC_PRIVATE;
param.h264_mod_param.qp_hist_en = HI_FALSE;
param.h264_mod_param.max_user_data_len = 1024;
int rc = hi_mpi_venc_set_mod_param(&param);
if (rc != HI_SUCCESS)
throw Poco::InvalidAccessException(
Poco::format("MPP5::VENC: Set module H264 param FAIL: %u(%#x)", (uint32_t)rc, (uint32_t)rc));
param.mod_type = HI_VENC_MOD_H265;
param.h265_mod_param.one_stream_buf = 0;
param.h265_mod_param.mini_buf_mode = 0;
param.h265_mod_param.low_power_mode = 0;
param.h265_mod_param.vb_src = HI_VB_SRC_PRIVATE;
param.h265_mod_param.qp_hist_en = HI_FALSE;
param.h265_mod_param.max_user_data_len = 32768;
rc = hi_mpi_venc_set_mod_param(&param);
if (rc != HI_SUCCESS)
throw Poco::InvalidAccessException(
Poco::format("MPP5::VENC: Set module H265 param FAIL: %u(%#x)", (uint32_t)rc, (uint32_t)rc));
}
void VideoEncoder::deinitModule()
{
}
}
}