/* ******************************************************************************* * INCLUDE ******************************************************************************* */ #include "VideoEncoder.h" #include "ResourceManager.h" #include "Logger.h" #include "util/Math.h" #include 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(*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(); 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(), ¶m); 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& 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& 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(); 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(¶m); 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(¶m); 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() { } } }