/* ******************************************************************************* * INCLUDE ******************************************************************************* */ #include "VideoDecoder.h" #include "ResourceManager.h" #include "Util.h" #include "Logger.h" #include "util/Task.h" #include "util/Math.h" #include "../../core/Demuxer.h" #include "../../core/System.h" #include "../../core/Notifications.h" #include "../v4l2/Input.h" #include #include #include #include #include #include #include #include #include namespace Video { namespace MPP5 { /* ******************************************************************************* * 追帧(catch-up)阈值配置 * 说明:所有阈值均以"队列中待解码视频包个数"为单位。 * 视频典型帧率 30fps,每包约 33ms,队列上限 300,约 10s 缓冲。 ******************************************************************************* */ namespace { constexpr int V_CATCH_UP_ENTER = 10; constexpr int V_CATCH_UP_EXIT = 2; } /* ******************************************************************************* * CLASS DEFINE ******************************************************************************* */ VideoDecoder::Pipe::Pipe(const std::string& name, uint32_t id) : MPPChannel((uint32_t)HI_ID_VPSS, id, 0), _name(Poco::format("%s(VPSS#%u)", name, id)), _width(0), _height(0) { } VideoDecoder::Pipe::~Pipe() { } void VideoDecoder::Pipe::start(uint32_t width, uint32_t height) { _width = width; _height = height; hi_vpss_grp_attr grpAttr = {}; grpAttr.nr_en = HI_FALSE; grpAttr.ie_en = HI_FALSE; grpAttr.dci_en = HI_FALSE; grpAttr.buf_share_en = HI_FALSE; grpAttr.buf_share_chn = 0; grpAttr.mcf_en = HI_FALSE; grpAttr.max_width = width; grpAttr.max_height = height; grpAttr.max_dei_width = width; grpAttr.max_dei_height = height; grpAttr.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_420; grpAttr.dynamic_range = HI_DYNAMIC_RANGE_SDR8; grpAttr.frame_rate.src_frame_rate = -1; grpAttr.frame_rate.dst_frame_rate = -1; grpAttr.dei_mode = HI_VPSS_DEI_MODE_OFF; int rc = hi_mpi_vpss_create_grp(groupID(), &grpAttr); 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 = width; chnAttr.height = height; chnAttr.video_format = HI_VIDEO_FORMAT_LINEAR; chnAttr.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_422; 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 VideoDecoder::Pipe::stop() { int rc = hi_mpi_vpss_disable_chn(groupID(), channelID()); if (rc != HI_SUCCESS) throw Poco::InvalidAccessException( Poco::format("%s: Disable channel#%u FAIL: %d(%#x)", name(), channelID(), rc, (uint32_t)rc), rc); rc = hi_mpi_vpss_stop_grp(groupID()); if (rc != HI_SUCCESS) throw Poco::InvalidAccessException(Poco::format("%s: Stop group FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); rc = hi_mpi_vpss_destroy_grp(groupID()); if (rc != HI_SUCCESS) throw Poco::InvalidAccessException(Poco::format("%s: Destroy group FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); } void VideoDecoder::Pipe::write(VideoFrame& frame, uint64_t pts, uint32_t timeref, bool isFrozen, hi_video_frame_info& frameX) { hi_video_frame_info* pInfo = isFrozen ? (hi_video_frame_info*)&frameX : (hi_video_frame_info*)frame.info(); pInfo->video_frame.pts = pts; pInfo->video_frame.time_ref = timeref; int rc = hi_mpi_vpss_send_frame(groupID(), pInfo, 100); if (rc != HI_SUCCESS) throw Poco::InvalidAccessException(Poco::format("%s: Send frame FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); } uint32_t VideoDecoder::Pipe::groupID() const { return deviceID(); } const std::string& VideoDecoder::Pipe::name() const { return _name; } const uint32_t VideoDecoder::Pipe::width() { return _width; } const uint32_t VideoDecoder::Pipe::height() { return _height; } /* ******************************************************************************* * CLASS DEFINE ******************************************************************************* */ Core::CodecCapabilityManager VideoDecoder::_vdhCapabilityManager(1920 * 1080 * 30 * 6); Core::CodecCapabilityManager VideoDecoder::_jpgdCapabilityManager(4096 * 2160 * 30); static const int poolId = 0; VideoDecoder::VideoDecoder(const std::string& name) : MPPChannel((uint32_t)HI_ID_VDEC, 0, ResourceManager::allocVDECChannelID()), Core::VideoDecoder(Poco::format("%s(MPP5::VDEC#%u)", name, channelID())), _handle(Core::CodecCapabilityManager::INVALID_HANDLE), _pipe(Core::VideoDecoder::name(), ResourceManager::allocVPSSGroupID()), _synchronizer(name + "/sync"), _channelId(Util::getChannelId(name)), _lastStatus(false), _catchUpEnabled(name.find("NET") != std::string::npos ? true : false), _isCatchingUp(false) { setWidth(NO_SIGNAL_YUV_WIDTH); setHeight(NO_SIGNAL_YUV_HEIGHT); setFrameRate(30); } VideoDecoder::~VideoDecoder() { ResourceManager::freeVPSSGroupID(_pipe.groupID()); ResourceManager::freeVENCChannelID(channelID()); releaseCapabilityHandle(); } void VideoDecoder::start() { task().run( [this](std::atomic& isCancelled) -> void { _pipe.start(NO_SIGNAL_YUV_WIDTH, NO_SIGNAL_YUV_HEIGHT); serialCodeWatcher().reset(); while (!isCancelled) { try { Core::Packet::Ptr pPacket = tryReadPacket(3000, true); if (pPacket.isNull()) { serialCodeWatcher().reset(); pPacket = runExceptionProcedure(isCancelled); } while (!isCancelled && !pPacket.isNull() && !handleSerialCode(pPacket->serialCode())) { serialCodeWatcher().reset(); pPacket = runExceptionProcedure(isCancelled); } if (!isCancelled && !pPacket.isNull()) decodePacket(pPacket, isCancelled); } catch (Poco::Exception& e) { logger().error("%s: %s", name(), e.displayText()); } catch (std::exception& e) { logger().error("%s: %s", name(), std::string(e.what())); } } releaseDecoder(true); _pipe.stop(); }); } void VideoDecoder::stop() { task().cancel(); } void VideoDecoder::initialize() { } void VideoDecoder::deinitialize() { } void VideoDecoder::onConfigChanged(ParameterNames& names) { if (names.exists(_SignalLost)) { } } Core::Frame::Ptr VideoDecoder::decode() { return Core::Frame::Ptr(); } void VideoDecoder::bind(const MPPChannel& next) { _pipe.bind(next); } void VideoDecoder::unbind(const MPPChannel& next) { _pipe.unbind(next); } void VideoDecoder::sendUserFrame(VideoFrame::Ptr pFrame, uint64_t duration, uint32_t& timeref) { uint64_t now = Core::System::timestamp(); _pipe.write(*pFrame, now, timeref, _frozenPlay, _frameX); timeref += 2; while (Core::System::timestamp() - now < duration) ::usleep(1000); } void VideoDecoder::runSendUserFrame(VideoFrame::Ptr pFrame, std::function condition) { uint64_t duration = (float)1000000 / getFrameRate(); while (condition()) { sendUserFrame(pFrame, duration, _frameTimeRef); _frameTimeRef += 2; } } void VideoDecoder::runSignalLost(std::atomic& isCancelled) { releaseDecoder(); std::atomic isSignalLost(true); ::Util::Task probeTask; probeTask.run( [&](std::atomic& isCancelled) -> void { while (!isCancelled) { if (!tryReadPacket(1000, false).isNull()) { isSignalLost = false; break; } } }); VideoFrame::Ptr pUserFrame = VideoFrame::create(NO_SIGNAL_YUV_FORMAT, NO_SIGNAL_YUV_WIDTH, NO_SIGNAL_YUV_HEIGHT, NO_SIGNAL_YUV_PATH); runSendUserFrame(pUserFrame, [&]() -> bool { return (!isCancelled && isSignalLost); }); probeTask.cancel(); } void VideoDecoder::runFault(std::atomic& isCancelled) { releaseDecoder(); std::atomic isFaultClean(false); ::Util::Task tryTask; tryTask.run( [&](std::atomic& isCancelled) -> void { while (!isCancelled) { Core::Packet::Ptr pPacket = tryReadPacket(1000, true); if (pPacket.isNull()) continue; if (handleSerialCode(pPacket->serialCode())) { isFaultClean = true; break; } } }); VideoFrame::Ptr pUserFrame = VideoFrame::create(NO_SIGNAL_YUV_FORMAT, NO_SIGNAL_YUV_WIDTH, NO_SIGNAL_YUV_HEIGHT, NO_SIGNAL_YUV_PATH); runSendUserFrame(pUserFrame, [&]() -> bool { return (!isCancelled && !isFaultClean); }); tryTask.cancel(); } Core::Packet::Ptr VideoDecoder::runExceptionProcedure(std::atomic& isCancelled) { releaseDecoder(); std::atomic isSignalLost(true); Core::Packet::Ptr pPacket = nullptr; ::Util::Task task; task.run( [&](std::atomic& isCancelled) -> void { while (!isCancelled) { pPacket = tryReadPacket(1000, false); if (!pPacket.isNull()) { isSignalLost = false; break; } } }); VideoFrame::Ptr pUserFrame = VideoFrame::create(NO_SIGNAL_YUV_FORMAT, NO_SIGNAL_YUV_WIDTH, NO_SIGNAL_YUV_HEIGHT, NO_SIGNAL_YUV_PATH); runSendUserFrame(pUserFrame, [&]() -> bool { return (!isCancelled && isSignalLost); }); task.cancel(); return pPacket; } bool VideoDecoder::handleSerialCode(const Core::SerialCode& code) { bool errorOccurred = false; if (serialCodeWatcher().isChanged(code, true)) { try { handlePreviousChanged(); } catch (Poco::Exception& e) { logger().error("%s: %s", name(), e.displayText()); errorOccurred = true; } catch (std::exception& e) { logger().error("%s: %s", name(), std::string(e.what())); errorOccurred = true; } } if (errorOccurred) releaseDecoder(); return !errorOccurred; } void VideoDecoder::handlePreviousChanged() { VideoComponent::Ptr pPreviousVideo = nullptr; Core::CodecID codecID = Core::CodecID::NB; auto pPrevious = previousComponents().first(); auto pDemuxer = pPrevious.cast(); if (pDemuxer) { auto pEncoder = pDemuxer->videoEncoder(); if (pEncoder) { pPreviousVideo = pEncoder; codecID = pEncoder->getCodecID(); } } if (!pPreviousVideo) { auto V4L2Input = pPrevious.cast(); if (V4L2Input) { pPreviousVideo = V4L2Input; codecID = V4L2Input->getCodecID(); } } if (!pPreviousVideo) throw Poco::NotImplementedException(Poco::format("Unsupported previous component: %s", pPrevious->name())); const auto width = pPreviousVideo->getWidth(); const auto height = pPreviousVideo->getHeight(); const auto framteRate = pPreviousVideo->getFrameRate(); setWidth(width); setHeight(height); setFrameRate(framteRate); ParameterNames parameters; config().getChangedParameters(parameters); if (_pipe.width() != width || _pipe.height() != height) { _pipe.stop(); _pipe.start(width, height); serialCodeManager().increment(); } hi_vdec_chn_status status; int rc = hi_mpi_vdec_query_status(channelID(), &status); logger().debug("%s: Query status: %#x, parameters changed:%d, %d, %d", name(), (uint32_t)rc, (int)!parameters.empty(), (int)status.type, (int)MPP5::Util::getPayloadType(codecID)); if (HI_ERR_VDEC_UNEXIST == rc || !parameters.empty() || status.type != MPP5::Util::getPayloadType(codecID)) { releaseDecoder(); createDecoder(width, height, framteRate, codecID); notifyNextComponents(sharedFromThis(), new Core::ParameterChangedNotification(parameters)); } } void VideoDecoder::decodePacket(Core::Packet::Ptr pPacket, std::atomic& isCancelled) { hi_vdec_stream st = {}; st.end_of_frame = HI_FALSE; st.end_of_stream = HI_FALSE; st.need_display = HI_TRUE; st.addr = (hi_u8*)pPacket->data(); st.len = pPacket->length(); st.pts = pPacket->pts(); int rc; do { rc = hi_mpi_vdec_send_stream(channelID(), &st, 1000); } while (!isCancelled && rc == HI_ERR_VDEC_BUF_FULL); } void VideoDecoder::createDecoder(uint32_t width, uint32_t height, uint32_t frameRate, Core::CodecID codecID) { logger().debug("%s: Initialize decoder: %s %ux%u@%u", name(), Core::codecIDToString(codecID), width, height, frameRate); hi_pic_buf_attr pic; pic.width = width; pic.height = height; pic.align = HI_DEFAULT_ALIGN; pic.bit_width = HI_DATA_BIT_WIDTH_8; pic.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_420; pic.compress_mode = HI_COMPRESS_MODE_NONE; hi_vdec_chn_attr attr; attr.type = MPP5::Util::getPayloadType(codecID); attr.mode = HI_VDEC_SEND_MODE_FRAME; attr.pic_width = width; attr.pic_height = height; attr.stream_buf_size = attr.pic_width * attr.pic_height * 3 / 2; attr.frame_buf_size = hi_vdec_get_pic_buf_size(attr.type, &pic); attr.frame_buf_cnt = 33; attr.video_attr.ref_frame_num = 16; attr.video_attr.temporal_mvp_en = HI_TRUE; attr.video_attr.tmv_buf_size = hi_vdec_get_tmv_buf_size(attr.type, width, height); int rc = hi_mpi_vdec_create_chn(channelID(), &attr); if (rc != HI_SUCCESS) throw Poco::InvalidAccessException(Poco::format("%s: Create chn FAIL: %#x", name(), (uint32_t)rc), rc); hi_mpi_vdec_reset_chn(channelID()); hi_vdec_chn_param param; hi_mpi_vdec_get_chn_param(channelID(), ¶m); param.type = attr.type; param.display_frame_num = 2; switch (param.type) { case HI_PT_H264: case HI_PT_H265: param.video_param.composite_dec_en = HI_FALSE; param.video_param.slice_input_en = HI_FALSE; param.video_param.dec_mode = HI_VIDEO_DEC_MODE_IPB; param.video_param.out_order = HI_VIDEO_OUT_ORDER_DISPLAY; param.video_param.compress_mode = HI_COMPRESS_MODE_NONE; param.video_param.video_format = HI_VIDEO_FORMAT_TILE_64x16; break; case HI_PT_JPEG: case HI_PT_MJPEG: param.pic_param.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_420; param.pic_param.alpha = 255; break; default: break; } rc = hi_mpi_vdec_set_chn_param(channelID(), ¶m); if (rc != HI_SUCCESS) throw Poco::InvalidAccessException(Poco::format("%s: Set param FAIL: %#x", name(), (uint32_t)rc), rc); rc = hi_mpi_vdec_set_display_mode(channelID(), HI_VIDEO_DISPLAY_MODE_PLAYBACK); if (rc != HI_SUCCESS) throw Poco::InvalidAccessException(Poco::format("%s: Set display mode FAIL: %#x", name(), (uint32_t)rc), rc); rc = hi_mpi_vdec_start_recv_stream(channelID()); if (rc != HI_SUCCESS) throw Poco::InvalidAccessException(Poco::format("%s: Start recv stream FAIL: %#x", name(), (uint32_t)rc), rc); _postTaskControl = PostTaskControl::RUNNING; _postTask.run( [this](std::atomic& isCancelled) -> void { _isCatchingUp = false; while (!isCancelled) { hi_video_frame_info frame; bool noAudio = false; int rc = hi_mpi_vdec_get_frame(channelID(), &frame, NULL, 100); if (rc < 0) { logger().warning("%s: Read decoded frame FAIL: %#x", name(), (uint32_t)rc); if (_postTaskControl == PostTaskControl::EXIT || _postTaskControl == PostTaskControl::INSTANT_EXIT) break; if (!_frozenPlay) continue; else noAudio = true; } if (_buttonPressed != _frozenPlay) { _buttonPressed = _frozenPlay; if (_frozenPlay) { saveSingleFrame(frame, "/customer/resources/frozen" + std::to_string(_channelId)); loadVideoFrame("/customer/resources/frozen" + std::to_string(_channelId), _frameX); } else { frameRelease(_frameX); std::string dir = "/customer/resources/frozen" + std::to_string(_channelId); std::string cmd = "rm -rf " + dir; if (system(cmd.c_str()) != 0) logger().warning("rm -rf %s failed", dir.c_str()); } } if (_postTaskControl == PostTaskControl::EXIT) { hi_vdec_chn_status status; if (hi_mpi_vdec_query_status(channelID(), &status) == HI_SUCCESS && status.left_decoded_frames == 0) { logger().debug("%s: No decoded frame, exit.", name()); isCancelled = true; } } else if (_postTaskControl == PostTaskControl::INSTANT_EXIT) { logger().debug("%s: Instant exit.", name()); isCancelled = true; } auto pAudio = getAssociatedAudio(); if (pAudio.isNull() || pAudio->getTimeline() == 0 || !pAudio->started() || noAudio) { if (!noAudio) { updateCatchUpState(); if (!_isCatchingUp) _synchronizer.syncFrame(frame.video_frame.pts); } } else { int64_t diff = frame.video_frame.pts - pAudio->getTimeline(); if (diff > 0) { if (diff > 1000000000000) diff = 1; else if (diff > 2000000) { logger().warning("%s: Video exceeds audio timeline by %Ld microseconds (%.1f seconds)", name(), diff, diff / 1000000.0); diff = 2000000; } std::this_thread::sleep_for(std::chrono::microseconds(diff)); } } frame.video_frame.pts = Core::System::timestamp(); frame.video_frame.time_ref = _frameTimeRef; _frameTimeRef += 2; _frameX.video_frame.pts = Core::System::timestamp(); _frameX.video_frame.time_ref = _frameTimeRef; if (_frozenPlay) hi_mpi_vpss_send_frame(_pipe.groupID(), &_frameX, 0); else { hi_mpi_vpss_send_frame(_pipe.groupID(), &frame, 0); } hi_mpi_vdec_release_frame(channelID(), &frame); } }); setSignalLost(false); commit(); } void VideoDecoder::releaseDecoder(bool instant) { logger().debug("%s: Deinitialize decoder...", name()); hi_vdec_chn_status status; if (hi_mpi_vdec_query_status(channelID(), &status) == HI_SUCCESS) { logger().debug("%s: Decoded frame left: %u", name(), status.left_decoded_frames); } _postTaskControl = instant ? PostTaskControl::INSTANT_EXIT : PostTaskControl::EXIT; int wait = 0; while (_postTask.isRunning()) { Poco::Thread::sleep(500); if (wait++ >= 10) { _postTask.cancel(); break; } } releaseCapabilityHandle(); hi_mpi_vdec_stop_recv_stream(channelID()); hi_mpi_vdec_destroy_chn(channelID()); setSignalLost(true); commit(); logger().debug("%s: Deinitialize decoder complete", name()); } Core::Packet::Ptr VideoDecoder::tryReadPacket(uint32_t timeout, bool shuldLog) { Core::Packet::Ptr pPacket; try { pPacket = readPacket(timeout); } catch (Poco::Exception& e) { if (shuldLog) logger().error("%s: %s", name(), e.displayText()); } catch (std::exception& e) { if (shuldLog) logger().error("%s: %s", name(), std::string(e.what())); } return pPacket; } void VideoDecoder::releaseCapabilityHandle() { } void VideoDecoder::initModule() { hi_vdec_mod_param param; hi_mpi_vdec_get_mod_param(¶m); param.vb_src = HI_VB_SRC_PRIVATE; param.mini_buf_mode = 0; param.pic_mod_param.capacity_strategy = HI_VDEC_CAPACITY_STRATEGY_BY_CHN; auto rc = hi_mpi_vdec_set_mod_param(¶m); if (rc != HI_SUCCESS) throw Poco::InvalidAccessException( Poco::format("MPP5::VDEC: Set module param FAIL: %u(%#x)", (uint32_t)rc, (uint32_t)rc)); } void VideoDecoder::deinitModule() { } void VideoDecoder::reset() { } void VideoDecoder::frameCopy(hi_video_frame_info& from, hi_video_frame_info& to) { uint32_t imageBytes, lumaBytes, chromaBytes; uint32_t stride = from.video_frame.stride[0]; uint32_t height = from.video_frame.height; switch (from.video_frame.pixel_format) { case HI_PIXEL_FORMAT_YVU_SEMIPLANAR_420: case HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420: imageBytes = stride * ::Util::Math::alignup(height, 16) * 3 / 2; lumaBytes = stride * ::Util::Math::alignup(height, 16); chromaBytes = lumaBytes >> 1; break; case HI_PIXEL_FORMAT_YVU_SEMIPLANAR_422: case HI_PIXEL_FORMAT_YUV_SEMIPLANAR_422: imageBytes = stride * ::Util::Math::alignup(height, 16) * 2; lumaBytes = stride * ::Util::Math::alignup(height, 16); chromaBytes = lumaBytes; break; default: throw Poco::NotImplementedException("Unsupported pixel format"); } hi_vb_blk handle = hi_mpi_vb_get_blk(poolId, imageBytes, "anonymous"); if (HI_VB_INVALID_HANDLE == handle) throw Poco::RuntimeException( Poco::format("VideoFrame: Failed to get VB block: %u bytes @POOL#%d", imageBytes, (int)poolId)); hi_phys_addr_t phyAddr = hi_mpi_vb_handle_to_phys_addr(handle); if (!phyAddr) throw Poco::RuntimeException("VideoFrame: Failed to get VB block phys-addr"); uint8_t* pVirAddr = (uint8_t*)hi_mpi_sys_mmap(phyAddr, imageBytes); if (!pVirAddr) throw Poco::RuntimeException("VideoFrame: Failed to map VB block"); memset(&to, 0, sizeof(hi_video_frame_info)); to.mod_id = HI_ID_USER; to.pool_id = poolId; memcpy(&to.video_frame, &from.video_frame, sizeof(hi_video_frame)); to.video_frame.phys_addr[0] = phyAddr; to.video_frame.phys_addr[1] = (phyAddr + lumaBytes); to.video_frame.virt_addr[0] = (hi_void*)pVirAddr; to.video_frame.virt_addr[1] = (hi_void*)(pVirAddr + lumaBytes); to.video_frame.time_ref = 0; to.video_frame.pts = 0; to.video_frame.frame_flag = 0; to.video_frame.user_data[0] = imageBytes; to.video_frame.user_data[1] = (hi_u64)pVirAddr; if (lumaBytes > 0) { hi_void* pFromVirAddr = hi_mpi_sys_mmap(from.video_frame.phys_addr[0], lumaBytes); memcpy(to.video_frame.virt_addr[0], pFromVirAddr, lumaBytes); hi_mpi_sys_munmap(pFromVirAddr, lumaBytes); } if (chromaBytes > 0) { hi_void* pFromVirAddr = hi_mpi_sys_mmap(from.video_frame.phys_addr[1], chromaBytes); memcpy(to.video_frame.virt_addr[1], pFromVirAddr, chromaBytes); hi_mpi_sys_munmap(pFromVirAddr, chromaBytes); } } void VideoDecoder::frameRelease(hi_video_frame_info& frame) { uint32_t imageBytes = frame.video_frame.user_data[0]; hi_void* pVirAddr = (hi_void*)frame.video_frame.user_data[1]; if (imageBytes && pVirAddr) { hi_sys_virt_mem_info info; if (hi_mpi_sys_get_virt_mem_info(pVirAddr, &info) == HI_SUCCESS) { hi_mpi_sys_munmap(pVirAddr, imageBytes); hi_mpi_vb_release_blk(hi_mpi_vb_phys_addr_to_handle(info.phys_addr)); } frame.video_frame.user_data[0] = 0; frame.video_frame.user_data[1] = 0; } } bool VideoDecoder::saveSingleFrame(hi_video_frame_info& frame, const std::string& dir_path) { mkdir("/customer/resources", 0755); mkdir(dir_path.c_str(), 0755); uint32_t stride = frame.video_frame.stride[0]; uint32_t width = frame.video_frame.width; uint32_t height = frame.video_frame.height; uint32_t pixel_format = frame.video_frame.pixel_format; uint32_t lumaBytes, chromaBytes, imageBytes; uint32_t aligned_height = ::Util::Math::alignup(height, 16); switch (pixel_format) { case HI_PIXEL_FORMAT_YVU_SEMIPLANAR_420: case HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420: lumaBytes = stride * aligned_height; chromaBytes = lumaBytes >> 1; imageBytes = lumaBytes + chromaBytes; break; case HI_PIXEL_FORMAT_YVU_SEMIPLANAR_422: case HI_PIXEL_FORMAT_YUV_SEMIPLANAR_422: lumaBytes = stride * aligned_height; chromaBytes = lumaBytes; imageBytes = lumaBytes + chromaBytes; break; default: throw Poco::NotImplementedException("Unsupported pixel format"); } std::string yuv_path = dir_path + "/frame.yuv"; FILE* fp_yuv = fopen(yuv_path.c_str(), "wb"); if (!fp_yuv) return false; hi_void* pLuma = hi_mpi_sys_mmap(frame.video_frame.phys_addr[0], lumaBytes); if (pLuma) { fwrite(pLuma, 1, lumaBytes, fp_yuv); hi_mpi_sys_munmap(pLuma, lumaBytes); } hi_void* pChroma = hi_mpi_sys_mmap(frame.video_frame.phys_addr[1], chromaBytes); if (pChroma) { fwrite(pChroma, 1, chromaBytes, fp_yuv); hi_mpi_sys_munmap(pChroma, chromaBytes); } fclose(fp_yuv); std::string info_path = dir_path + "/frame.bin"; int fd = open(info_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) return false; hi_video_frame_info frame_to_save = frame; for (int i = 0; i < OT_MAX_COLOR_COMPONENT; i++) { frame_to_save.video_frame.virt_addr[i] = NULL; frame_to_save.video_frame.header_virt_addr[i] = NULL; } uint32_t extra_info[4] = {lumaBytes, chromaBytes, imageBytes, aligned_height}; write(fd, extra_info, sizeof(extra_info)); write(fd, &frame_to_save, sizeof(hi_video_frame_info)); close(fd); sync(); logger().debug("Saved: %s and %s", yuv_path, info_path); return true; } bool VideoDecoder::loadVideoFrame(const std::string& dir_path, hi_video_frame_info& frame) { std::string info_path = dir_path + "/frame.bin"; int fd = open(info_path.c_str(), O_RDONLY); if (fd < 0) return false; uint32_t extra_info[4] = {0}; read(fd, extra_info, sizeof(extra_info)); uint32_t lumaBytes = extra_info[0]; uint32_t chromaBytes = extra_info[1]; uint32_t imageBytes = extra_info[2]; uint32_t aligned_height = extra_info[3]; hi_video_frame_info loaded_frame; memset(&loaded_frame, 0, sizeof(loaded_frame)); read(fd, &loaded_frame, sizeof(hi_video_frame_info)); close(fd); hi_vb_blk handle = hi_mpi_vb_get_blk(poolId, imageBytes, "loaded_frame"); if (HI_VB_INVALID_HANDLE == handle) { logger().warning("Failed to get VB block: %u bytes @POOL#%d", imageBytes, poolId); return false; } hi_phys_addr_t phyAddr = hi_mpi_vb_handle_to_phys_addr(handle); if (!phyAddr) { logger().warning("Failed to get VB block phys-addr\n"); hi_mpi_vb_release_blk(handle); return false; } uint8_t* pVirAddr = (uint8_t*)hi_mpi_sys_mmap(phyAddr, imageBytes); if (!pVirAddr) { logger().warning("Failed to map VB block"); hi_mpi_vb_release_blk(handle); return false; } std::string yuv_path = dir_path + "/frame.yuv"; FILE* fp_yuv = fopen(yuv_path.c_str(), "rb"); if (!fp_yuv) { logger().warning("Failed to open: %s", yuv_path); hi_mpi_sys_munmap(pVirAddr, imageBytes); hi_mpi_vb_release_blk(handle); return false; } size_t bytes_read = fread(pVirAddr, 1, imageBytes, fp_yuv); fclose(fp_yuv); if (bytes_read != imageBytes) { logger().warning("Read incomplete: %zu/%u bytes", bytes_read, imageBytes); hi_mpi_sys_munmap(pVirAddr, imageBytes); hi_mpi_vb_release_blk(handle); return false; } memset(&frame, 0, sizeof(hi_video_frame_info)); memcpy(&frame.video_frame, &loaded_frame.video_frame, sizeof(hi_video_frame)); frame.video_frame.phys_addr[0] = phyAddr; frame.video_frame.phys_addr[1] = phyAddr + lumaBytes; frame.video_frame.virt_addr[0] = pVirAddr; frame.video_frame.virt_addr[1] = (hi_void*)(pVirAddr + lumaBytes); frame.video_frame.user_data[0] = imageBytes; frame.video_frame.user_data[1] = (hi_u64)pVirAddr; logger().debug("Loaded frame: %ux%u, pts=%Lu", frame.video_frame.width, frame.video_frame.height, loaded_frame.video_frame.pts); return true; } void VideoDecoder::updateCatchUpState() { if (!_catchUpEnabled) return; hi_vdec_chn_status status; hi_mpi_vdec_query_status(channelID(), &status); const int queueSize = status.left_decoded_frames; if (!_isCatchingUp && queueSize > V_CATCH_UP_ENTER) { _isCatchingUp = true; logger().information("%s: video catch-up enter (queue=%d)", name(), queueSize); } else if (_isCatchingUp && queueSize <= V_CATCH_UP_EXIT) { _isCatchingUp = false; _synchronizer.reset(); logger().information("%s: video catch-up exit (queue=%d)", name(), queueSize); } } } }