1000 lines
32 KiB
C++
1000 lines
32 KiB
C++
/*
|
||
*******************************************************************************
|
||
* 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 <hi_mpi_vdec.h>
|
||
#include <hi_mpi_vpss.h>
|
||
#include <hi_mpi_sys.h>
|
||
#include <hi_buffer.h>
|
||
#include <Poco/Format.h>
|
||
#include <pthread.h>
|
||
#include <sys/stat.h>
|
||
#include <sys/types.h>
|
||
#include <dirent.h>
|
||
|
||
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<bool>& 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<bool(void)> condition)
|
||
{
|
||
uint64_t duration = (float)1000000 / getFrameRate();
|
||
|
||
while (condition())
|
||
{
|
||
sendUserFrame(pFrame, duration, _frameTimeRef);
|
||
_frameTimeRef += 2;
|
||
}
|
||
}
|
||
|
||
void VideoDecoder::runSignalLost(std::atomic<bool>& isCancelled)
|
||
{
|
||
releaseDecoder();
|
||
|
||
std::atomic<bool> isSignalLost(true);
|
||
::Util::Task probeTask;
|
||
probeTask.run(
|
||
[&](std::atomic<bool>& 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<bool>& isCancelled)
|
||
{
|
||
releaseDecoder();
|
||
|
||
std::atomic<bool> isFaultClean(false);
|
||
::Util::Task tryTask;
|
||
tryTask.run(
|
||
[&](std::atomic<bool>& 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<bool>& isCancelled)
|
||
{
|
||
releaseDecoder();
|
||
|
||
std::atomic<bool> isSignalLost(true);
|
||
Core::Packet::Ptr pPacket = nullptr;
|
||
::Util::Task task;
|
||
task.run(
|
||
[&](std::atomic<bool>& 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<Core::Demuxer>();
|
||
if (pDemuxer)
|
||
{
|
||
auto pEncoder = pDemuxer->videoEncoder();
|
||
if (pEncoder)
|
||
{
|
||
pPreviousVideo = pEncoder;
|
||
codecID = pEncoder->getCodecID();
|
||
}
|
||
}
|
||
|
||
if (!pPreviousVideo)
|
||
{
|
||
auto V4L2Input = pPrevious.cast<V4L2::Input>();
|
||
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<bool>& 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<bool>& 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);
|
||
}
|
||
}
|
||
}
|
||
}
|