概述
hysVideoQC (video quality comparator) 视频质量比较工具
基于开源项目VMAF和FFMPEG开发的一款 QT 图形界面工具。它可用于对比转码前后的视频失真变化,跟踪不同编码参数对视频转码的影响。
此前工具未对外,因此使用者很少,软件可能存在一些作者未发现的问题。希望使用的朋友多多包涵。如果使用过程中遇到问题或有好的建议,请不吝指正!(微信:hybase 添加请注明hysVideoQC或vmaf),先致谢!
hysVideoQC 下载地址(含使用 视频指南):
* 百度网盘 链接:https://pan.baidu.com/s/18esWF4m30fDlriKtE126KA 提取码:vmaf
* Github 网址:https://github.com/zymill/hysVideoQC
任务管理代码
//! ctor
vqc_task_c::vqc_task_c(void *pParent, UpdateTaskStatusFunc pFunc)
: m_pParent(pParent)
, m_pFuncb(pFunc)
{
m_pThread = nullptr;
m_bExitFlag = false;
m_pMutex = SDL_CreateMutex();
m_pMutexFrame = SDL_CreateMutex();
assert (m_pMutex && m_pMutexFrame);
initDefaultParams();
}
void vqc_task_c::initDefaultParams()
{
m_bInitialized = false;
m_nTargetFrames = 0;
m_tTargetDurationMS = 0;
m_nTargetFileSize = 0;
m_nLastPercent = -1;
m_tLastSleepS = iTimer::m_tNowS;
m_nStatus = VQC_TASK_NONE;
}
//! dtor
vqc_task_c::~vqc_task_c()
{
Release();
SDL_DestroyMutex(m_pMutex);
SDL_DestroyMutex(m_pMutexFrame);
}
void vqc_task_c::Release()
{
m_bExitFlag = true;
if (m_pThread)
{
SDL_WaitThread(m_pThread, nullptr);
m_pThread = nullptr;
}
foreach (SYUVFrameBufItem *pFrameItem, m_lstRefDecodedFrame)
{
if (pFrameItem)
{
if (pFrameItem->pFrame)
av_frame_free(&pFrameItem->pFrame);
delete pFrameItem;
}
}
m_lstRefDecodedFrame.clear();
foreach(SYUVFrameBufItem *pFrameItem, m_lstDstDecodedFrame)
{
if (pFrameItem)
{
if (pFrameItem->pFrame)
av_frame_free(&pFrameItem->pFrame);
delete pFrameItem;
}
}
m_lstDstDecodedFrame.clear();
m_nTargetFrames = 0;
m_tTargetDurationMS = 0;
m_nTargetFileSize = 0;
}
void vqc_task_c::initVmafContext(LIBVMAFContext &s)
{
m_stVmafContext = s;
}
void vqc_task_c::setVmafModelName(QString strVmafModelName) {
m_stVmafContext.strModelName = strVmafModelName;
}
void vqc_task_c::setLogFmt(QString strLogFmt) {
m_stVmafContext.strLogFmt = strLogFmt;
}
void vqc_task_c::setPSNR(bool bEnable) {
m_stVmafContext.bEnablePSNR = bEnable;
}
void vqc_task_c::setSSIM(bool bEnable) {
m_stVmafContext.bEnableSSIM = bEnable;
}
void vqc_task_c::setMsSSIM(bool bEnable){
m_stVmafContext.bEnableMSSSIM = bEnable;
}
void vqc_task_c::setTransform(bool bEnable) {
m_stVmafContext.bEnableTransform = bEnable;
}
void vqc_task_c::setConfInterval(bool bEnable) {
m_stVmafContext.bEnableConfInterval = bEnable;
}
void vqc_task_c::setPhoneModel(bool bPhoneModel) {
m_stVmafContext.bPhoneModel = bPhoneModel;
}
void vqc_task_c::setDisableClip(bool bDisableClip) {
m_stVmafContext.bDisableClip = bDisableClip;
}
void vqc_task_c::setDisableAVX(bool bDisableAVX) {
m_stVmafContext.bDisableAVX = bDisableAVX;
}
void vqc_task_c::setThreads(int nThreads) {
if (nThreads >= 0)
m_stVmafContext.nThreads = nThreads;
else
m_stVmafContext.nThreads = 0;
}
void vqc_task_c::setTargetParameters(int nFrames, int64_t tDurationMS, int64_t nTargetFileSize)
{
if (nFrames > 0)
m_nTargetFrames = nFrames;
else
m_nTargetFrames = 0;
if (tDurationMS > 0)
m_tTargetDurationMS = tDurationMS;
else
m_tTargetDurationMS = 0;
if (nTargetFileSize > 0)
m_nTargetFileSize = nTargetFileSize;
else
m_nTargetFileSize = 0;
LOG_DEBUG("set target: frames=%d, duration ms=%lld, filesize=%lld", m_nTargetFrames, tDurationMS, m_nTargetFileSize);
}
int vqc_task_c::Initialize(LIBVMAFContext &s0, int w, int h, AVPixelFormat nPixelFmt, time_t tDurationMS, AVRational stTimebase,
QString strRefUrl, QString strDstUrl, QString strLogUrl)
{
int nRet = 0;
LIBVMAFContext &s = m_stVmafContext;
s = s0;
if (w == 0)
return APP_ERR_VQC_INVALID_VIDEO_WIDTH;
if (h == 0)
return APP_ERR_VQC_INVALID_VIDEO_HEIGHT;
//! set YUVJ to YUV if necessary
if (nPixelFmt == AV_PIX_FMT_YUVJ420P)
nPixelFmt = AV_PIX_FMT_YUV420P;
else if (nPixelFmt == AV_PIX_FMT_YUVJ422P)
nPixelFmt = AV_PIX_FMT_YUVJ422P;
else if (nPixelFmt == AV_PIX_FMT_YUVJ444P)
nPixelFmt = AV_PIX_FMT_YUV444P;
if (nPixelFmt != AV_PIX_FMT_YUV420P &&
nPixelFmt != AV_PIX_FMT_YUV420P10LE &&
nPixelFmt != AV_PIX_FMT_YUV420P12LE &&
nPixelFmt != AV_PIX_FMT_YUV420P16LE &&
nPixelFmt != AV_PIX_FMT_YUV422P &&
nPixelFmt != AV_PIX_FMT_YUV422P10LE &&
nPixelFmt != AV_PIX_FMT_YUV422P12LE &&
nPixelFmt != AV_PIX_FMT_YUV422P16LE &&
nPixelFmt != AV_PIX_FMT_YUV444P &&
nPixelFmt != AV_PIX_FMT_YUV444P10LE &&
nPixelFmt != AV_PIX_FMT_YUV444P12LE &&
nPixelFmt != AV_PIX_FMT_YUV444P16LE)
{
return APP_ERR_VQC_INVALID_VIDEO_PIXEL_FORMAT;
}
s.nPixelFmt = nPixelFmt;
s.pDesc = av_pix_fmt_desc_get(nPixelFmt);
s.nWidth = w;
s.nHeight = h;
s.nFrameSize = av_image_get_buffer_size(nPixelFmt, w, h, 1);
s.nSubSample = 1; // always with all frames
s.error = 0;
nRet = initFiles(tDurationMS, stTimebase, strRefUrl, strDstUrl, s.strModelName, strLogUrl, s.strLogFmt);
if (0 != nRet)
{
goto ERROR_EXIT;
}
s.vmaf_thread_created = 1; //! always vmaf thread created
s.frame_set = 0;
m_pThread = SDL_CreateThread(vqc_task_c::ThreadProc, "workProc", this);
if (nullptr == m_pThread)
{
LOG_ERROR("SDL_CreateThread() failed");
goto ERROR_EXIT;
}
m_bInitialized = true;
changeStatus(VQC_TASK_INIT);
LOG_INFO("init success, dst_url=" + strDstUrl);
return 0;
ERROR_EXIT:
Release();
changeStatus(VQC_TASK_FAILURE);
return nRet;
}
int vqc_task_c::initFiles(time_t tDurationMS, AVRational stTimebase, QString strRefUrl, QString strDstUrl, QString strModelPath, QString strLogPath, QString strLogFmt)
{
STaskYUVFileItem &f = m_stTaskFile;
f.strRefUrl = strRefUrl;
f.strDstUrl = strDstUrl;
f.bRefEOF = false;
f.bDstEOF = false;
f.tRefDurationMS = tDurationMS;
f.stRefTimebase = stTimebase;
f.tRefLastPts = AV_NOPTS_VALUE;
f.tRefStartPts = AV_NOPTS_VALUE;
f.nRefFileSize = Util::getQStringFileSize(strRefUrl);
f.strModelPath = strModelPath.toStdString();
f.strLogFmt = strLogFmt.toStdString();
f.strLogPath = strLogPath.toStdString();
LOG_DEBUG("init ok, refer file timebase:%d/%d, duration_ms=%lld, ref_url=%s, dst_url=%s",
stTimebase.num, stTimebase.den, tDurationMS,
strRefUrl.toStdString().c_str(), strDstUrl.toStdString().c_str());
return 0;
}
bool vqc_task_c::isProcessRefToolSlow()
{
bool bRet = false;
lockFrame();
if (m_lstRefDecodedFrame.size() >= 5)
bRet = true;
unlockFrame();
return bRet;
}
bool vqc_task_c::isProcessDstToolSlow()
{
bool bRet = false;
lockFrame();
if (m_lstDstDecodedFrame.size() >= 5)
bRet = true;
unlockFrame();
return bRet;
}
int vqc_task_c::pushRefFrame(SYUVFrameBufItem *pFrameItem)
{
lockFrame();
if (pFrameItem)
{
if (pFrameItem->pFrame)
m_stTaskFile.nRefRecvFrame++;
m_lstRefDecodedFrame.push_back(pFrameItem);
if (pFrameItem->nFrameIndex%50 == 0)
LOG_DEBUG("ref frame_idx=%lld, lst_sz=%llu", pFrameItem->nFrameIndex, m_lstRefDecodedFrame.size());
}
unlockFrame();
return 0;
}
int vqc_task_c::pushDstFrame(SYUVFrameBufItem *pFrameItem)
{
lockFrame();
if (pFrameItem)
{
if (pFrameItem->pFrame)
m_stTaskFile.nDstRecvFrame++;
m_lstDstDecodedFrame.push_back(pFrameItem);
if (pFrameItem->nFrameIndex%1 == 0)
LOG_DEBUG("dst frame_idx=%lld, lst_sz= %llu", pFrameItem->nFrameIndex, m_lstDstDecodedFrame.size());
}
unlockFrame();
return 0;
}
bool vqc_task_c::syncFrame()
{
lockFrame();
bool bRefFrameReady = !m_lstRefDecodedFrame.empty();
bool bDstFrameReady = !m_lstDstDecodedFrame.empty();
unlockFrame();
return bRefFrameReady && bDstFrameReady;
}
void vqc_task_c::readRefDecodedFrame(float *pRefData, int nStrideBytes)
{
lockFrame();
if (!m_lstRefDecodedFrame.empty())
{
SYUVFrameBufItem *pFrameItem = m_lstRefDecodedFrame.front();
m_lstRefDecodedFrame.pop_front();
if (nullptr != pFrameItem->pFrame)
{
if (m_stTaskFile.tRefStartPts == AV_NOPTS_VALUE)
m_stTaskFile.tRefStartPts = pFrameItem->pFrame->pts;
m_stTaskFile.tRefLastPts = pFrameItem->pFrame->pts;
m_stTaskFile.nLastStreamPos = pFrameItem->pFrame->pkt_pos;
fillFrame(pFrameItem->pFrame, pRefData, nStrideBytes);
av_frame_free(&pFrameItem->pFrame);
LOG_TRACE("read ref frame_idx=%lld, pts=%lld, lst_sz=%llu, url=%s",
pFrameItem->nFrameIndex, m_stTaskFile.tRefLastPts,
m_lstRefDecodedFrame.size(), m_stTaskFile.strRefUrl.toStdString().c_str());
}
else
{
m_stTaskFile.bRefEOF = true;
LOG_DEBUG("!=== Ref EOF ===(null pointer come), end frame_index= %lld, lst_sz= %llu, ref_url=%s", pFrameItem->nFrameIndex,
m_lstRefDecodedFrame.size(), m_stTaskFile.strRefUrl.toStdString().c_str());
}
delete pFrameItem;
}
unlockFrame();
}
void vqc_task_c::readDstDecodedFrame(float *pDstData, int nStrideBytes)
{
lockFrame();
if (!m_lstDstDecodedFrame.empty())
{
SYUVFrameBufItem *pFrameItem = m_lstDstDecodedFrame.front();
m_lstDstDecodedFrame.pop_front();
if (nullptr != pFrameItem->pFrame)
{
fillFrame(pFrameItem->pFrame, pDstData, nStrideBytes);
av_frame_free(&pFrameItem->pFrame);
LOG_TRACE("read dst frame_idx=%lld, lst_sz=%llu, url=%s",
pFrameItem->nFrameIndex, m_lstDstDecodedFrame.size(),
m_stTaskFile.strDstUrl.toStdString().c_str());
}
else
{
m_stTaskFile.bDstEOF = true;
LOG_DEBUG("!=== Dst EOF ===(null pointer come), end frame_index= %d, lst_sz= %llu, dst_url=%s", m_stTaskFile.nReadFrame,
m_lstDstDecodedFrame.size(), m_stTaskFile.strDstUrl.toStdString().c_str());
}
delete pFrameItem;
}
unlockFrame();
}
void vqc_task_c::fillFrame(AVFrame *pFrame, float *pData, int nStrideBytes)
{
int i = 0, j = 0;
float *dst = pData;
AVFrame *sf = pFrame;
int w = sf->width;
int h = sf->height;
int nBits = vqc_bitdepth_ffmpeg_pixelfmt((AVPixelFormat)sf->format);
float factor = 1.f / (1 << (nBits - 8));
if (nBits <= 8)
{
const uint8_t *src = (const uint8_t*)sf->data[0];
for (i = 0; i < h; i++)
{
for (j = 0; j < w; j++)
dst[j] = src[j] * factor;
src += sf->linesize[0];
dst += nStrideBytes / sizeof(*dst);
}
}
else
{// 10/12/16-bit
const uint16_t *src = (const uint16_t*)sf->data[0];
for (i = 0; i < h; i++)
{
for (j = 0; j < w; j++)
dst[j] = src[j] * factor;
src += sf->linesize[0]/2 ;
dst += nStrideBytes / sizeof(*dst);
}
}
assert(pData + nStrideBytes * h/sizeof(*dst) >= dst);
}
int vqc_task_c::ReadFrameCallback(float *pRefData, float *pDstData, float *pTempData, int nStrideBytes, void *pParam)
{
return static_cast<vqc_task_c*>(pParam)->onReadFrame(pRefData, pDstData, pTempData, nStrideBytes);
}
#define VQC_READ_OK 0
#define VQC_READ_ERR 1
#define VQC_READ_EOF 2
//! return 0-success, 1-error, 2-eof
int vqc_task_c::onReadFrame(float *pRefData, float *pDstData, float *pTempData, int nStrideBytes)
{
STaskYUVFileItem &f = m_stTaskFile;
LIBVMAFContext &s = m_stVmafContext;
time_t tTimeoutS = 0;
time_t tStartS = iTimer::m_tNowS;
while (1)
{
if (m_bExitFlag)
{
m_nStatus = VQC_TASK_TERMINATED;
LOG_INFO("!=== task be terminated: read frames= %d (durations ms=%lld) %s",
f.nReadFrame, f.tRefDurationMS, m_szInfo);
return VQC_READ_EOF;
}
if (syncFrame())
break;
SDL_Delay(5);
m_tLastSleepS = iTimer::m_tNowS;
tTimeoutS = iTimer::m_tNowS - tStartS;
if (tTimeoutS >= 30)
{
int ncount = 3;
while (ncount--)
{
lockFrame();
LOG_INFO("!=== syncFrame() timeout %lld s, reflist= %llu, dstlist= %llu, "
"task be terminated, read frames=%lld(target ms= %lld) %s",
tTimeoutS, m_lstRefDecodedFrame.size(), m_lstDstDecodedFrame.size(),
f.nReadFrame, f.tRefDurationMS, m_szInfo);
unlockFrame();
}
return VQC_READ_ERR;
}
}
if (m_tLastSleepS + 5 < iTimer::m_tNowS)
{
m_tLastSleepS = iTimer::m_tNowS;
SDL_Delay(5);
}
//! reference file
readRefDecodedFrame(pRefData, nStrideBytes);
readDstDecodedFrame(pDstData, nStrideBytes);
if (f.bRefEOF || f.bDstEOF)
{
if (m_pParent && m_pFuncb)
{
SStatusMsg stMsg;
stMsg.strUrl = f.strDstUrl;
stMsg.nType = UPDATE_TYPE_PROGRESS;
stMsg.nStatus = VQC_TASK_RUNNING;
stMsg.nFrameCount= f.nReadFrame;
stMsg.nProgress = 100;
m_pFuncb(stMsg, m_pParent);
}
LOG_DEBUG("read %d frames(recv frames: ref=%d, dst=%d), dst_url=%s, %s %s",
f.nReadFrame, f.nRefRecvFrame, f.nDstRecvFrame,
f.strDstUrl.toStdString().c_str(),
(f.bRefEOF ? "=== Ref EOF ===" : ""),
(f.bDstEOF ? "=== Dst EOF ===" : ""));
return VQC_READ_EOF;
}
f.nReadFrame++;
if (f.nReadFrame%50 == 0)
LOG_DEBUG("read %lld frames(recv frames: ref=%lld, dst=%lld), dst_url=%s",
f.nReadFrame, f.nRefRecvFrame, f.nDstRecvFrame,
f.strDstUrl.toStdString().c_str());
//! arrive to target frames
if (m_nTargetFrames > 0 && f.nReadFrame >= m_nTargetFrames)
{
if (m_pParent && m_pFuncb)
{
SStatusMsg stMsg;
stMsg.strUrl = f.strDstUrl;
stMsg.nType = UPDATE_TYPE_PROGRESS;
stMsg.nStatus = VQC_TASK_RUNNING;
stMsg.nFrameCount= f.nReadFrame;
stMsg.nProgress = 100;
m_pFuncb(stMsg, m_pParent);
}
LOG_DEBUG("read %d frames, arrvie to target frames %d(recv frames: ref=%d, dst=%d), dst_url=%s, %s %s",
f.nReadFrame, m_nTargetFrames, f.nRefRecvFrame, f.nDstRecvFrame,
f.strDstUrl.toStdString().c_str(),
(f.bRefEOF ? "=== Ref EOF ===" : ""),
(f.bDstEOF ? "=== Dst EOF ===" : ""));
return VQC_READ_EOF;
}
if (m_pParent && m_pFuncb)
{
SStatusMsg stMsg;
if (m_tTargetDurationMS > 0)
{
time_t tAgeMS = 0;
if (f.tRefLastPts != AV_NOPTS_VALUE && f.tRefStartPts != AV_NOPTS_VALUE)
tAgeMS = av_rescale_q(f.tRefLastPts - f.tRefStartPts, f.stRefTimebase, g_avRationlBase)/1000;
stMsg.nProgress = 100 * tAgeMS / m_tTargetDurationMS;
if (stMsg.nProgress != f.nLastProgressPercent)
LOG_DEBUG("=== %d%% === curr_pos=%lld, age: ms=%lld, pts_diff=%lld(%lld ms)", stMsg.nProgress, tAgeMS,
f.tRefLastPts - f.tRefStartPts, (f.tRefLastPts - f.tRefStartPts)/90);
}
else if (m_nTargetFileSize > 0)
{
stMsg.nProgress = 100 * f.nLastStreamPos / m_nTargetFileSize;
if (stMsg.nProgress != f.nLastProgressPercent)
LOG_DEBUG("=== %d%% === curr_pos=%lld, target filesize=%lld", stMsg.nProgress, f.nLastStreamPos, m_nTargetFileSize);
}
else
{
LOG_DEBUG("=== %d%% ===", stMsg.nProgress); // do always with same 0, donot want arrive here
}
stMsg.strUrl = f.strDstUrl;
stMsg.nType = UPDATE_TYPE_PROGRESS;
stMsg.nStatus = VQC_TASK_RUNNING;
stMsg.nFrameCount= f.nReadFrame;
f.nLastProgressPercent = stMsg.nProgress;
if (m_nLastPercent != f.nLastProgressPercent ||
stMsg.nFrameCount%50 == 0 ||
stMsg.nProgress >= 100)
{
m_pFuncb(stMsg, m_pParent);
m_nLastPercent = f.nLastProgressPercent;
}
if (stMsg.nProgress >= 100 &&
((m_nTargetFileSize > 0 && m_nTargetFileSize != f.nRefFileSize) ||
(m_tTargetDurationMS > 0 && m_tTargetDurationMS != f.tRefDurationMS))
)
{
return VQC_READ_EOF; // arrive to target filesize or duration us;
}
}
return VQC_READ_OK;
}
void vqc_task_c::compute_vmaf_score(LIBVMAFContext &s, STaskYUVFileItem &f)
{
if (m_pParent && m_pFuncb)
{
SStatusMsg stMsg;
stMsg.strUrl = f.strDstUrl;
stMsg.nType = UPDATE_TYPE_PROGRESS;
stMsg.nStatus = m_nStatus;
stMsg.nProgress = 0;
stMsg.nFrameCount= 0;
m_pFuncb(stMsg, m_pParent);
}
...
if (m_nStatus != VQC_TASK_TERMINATED)
{
if (s.error == 0)
{
LOG_DEBUG("score=%f, dst_url= %s", s.vmaf_score, m_stTaskFile.strDstUrl.toStdString().c_str());
changeStatus(VQC_TASK_PARSE_RESULT);
}
else
{
LOG_ERROR("!=== error=%d, dst_url= %s", s.error, m_stTaskFile.strDstUrl.toStdString().c_str());
changeStatus(VQC_TASK_FAILURE);
}
}
}
int vqc_task_c::Start(){return 0;}
void vqc_task_c::Stop(){}
int vqc_task_c::getProgress(int &nFrameCount, double &dfProgress)
{
STaskYUVFileItem &f = m_stTaskFile;
nFrameCount = f.nReadFrame;
if (f.tRefStartPts != AV_NOPTS_VALUE && f.tRefLastPts != AV_NOPTS_VALUE && f.tRefDurationMS > 0)
{
time_t tAgeMS = 0;
int64_t val = f.tRefLastPts - f.tRefStartPts;
if (val < 0)
val = 0;
tAgeMS = av_rescale_q(val, g_avRationlBase, f.stRefTimebase)/1000;
dfProgress = 100.0 * tAgeMS/f.tRefDurationMS;
}
else
{
dfProgress = 100.0 * f.nLastStreamPos/f.nRefFileSize;
}
return 0;
}
int vqc_task_c::workProc()
{
while (1)
{
uint32_t nSleepMS = 10;
Lock(__LINE__);
switch (m_nStatus)
{
case VQC_TASK_INIT:
nSleepMS = 100;
break;
case VQC_TASK_RUNNING :
{
compute_vmaf_score(m_stVmafContext, m_stTaskFile);
nSleepMS = 0;
break;
}
// exit threads if vmaf end success, terminated or failure
case VQC_TASK_PARSE_RESULT:
{
QString strLogUrl = m_stTaskFile.strLogPath.c_str();
LOG_DEBUG("success, break from thread loop");
m_bExitFlag = true;
break;
}
case VQC_TASK_TERMINATED:
LOG_DEBUG("terminated, break from thread loop");
m_bExitFlag = true;
break;
case VQC_TASK_FAILURE:
LOG_ERROR("!=== failure, break from thread loop, errno=%d(%s), dst_url=%s",
m_stVmafContext.error,
strerror(-m_stVmafContext.error),
m_stTaskFile.strDstUrl.toStdString().c_str());
m_bExitFlag = true;
break;
}
Unlock(__LINE__);
if (m_bExitFlag)
{
break;
}
if (nSleepMS > 0)
SDL_Delay(nSleepMS);
}
if (m_pParent && m_pFuncb)
{
SStatusMsg stMsg;
stMsg.strUrl = m_stTaskFile.strDstUrl;
stMsg.nType = UPDATE_TYPE_STATUS;
stMsg.nStatus = m_nStatus;
if (m_nStatus == VQC_TASK_PARSE_RESULT)
{
QString strErr;
m_pFuncb(stMsg, m_pParent);
vmaf_log_c *pstVmafLog = new vmaf_log_c("", m_stTaskFile.strLogPath.c_str());
if (0 != pstVmafLog->doParse(strErr))
{
QString strTitle = "Vmaf log parse result";
QString strInfo = "Failed, ";
strInfo = strInfo + m_stTaskFile.strLogPath.c_str() + "\n" + strErr;
QMessageBox::warning(nullptr, strTitle, strInfo);
delete pstVmafLog;
pstVmafLog = nullptr;// delete if failed
changeStatus(VQC_TASK_FAILURE);
}
else
{
changeStatus(VQC_TASK_SUCCESS);
}
stMsg.pstVmafLog = pstVmafLog;
}
stMsg.nStatus = m_nStatus;
m_pFuncb(stMsg, m_pParent);
}
LOG_DEBUG("exit thread, dst_url=%s", m_stTaskFile.strDstUrl.toStdString().c_str());
return 0;
}
功能特征
1.Reference file (源视频文件)
* Support MP4/MPEGTS/MKV/AVI/FLV/3GP/MOV/VOB/MXF/MPG/M2TS
* Support ES files: h264/h265(*.264 *.265 *.266 *.h264 *.h265 *.h266) (266/h266 in future)
* Support ES files: m2v/m4v/mpeg(*.m2v *.m4v *.mpeg)
* Support ES files: AVS/AVS2/AVS3(*.avs *.avs2 *.avs3) (avs3 in future)
* Support ES files: VP8/VP9/AV1(*.vp8 *.vp9 *.av1)
* Support Raw YUV(*.yuv *.raw *.y4m) Files
-
Distortion files (转码后的文件)
- Support file format same as reference file
- Maximum support 8 files in one QC task
- Support file details based on FFMPEG
-
VMAF options
- Support PSNR/SSIM/MS_SSIM/VMAF
- Support JSON/XML/CSV files based on VMAF
- Support compare part of file if necessary.
-
Basic options
- Support YUV parameters setting for Raw file
- Support open, delete, reset reference/distortion file(s)
- Support start/stop compare task.
界面说明
视频指南
视频指南: (请参考百度盘中的 guide720p.mp4)
注意事项
1.系统要求
* windows 64 系统
* 内存 >= 4GB
2.参数
* 多线程建议维持在1/2 (除非系统支持 >= 48线程数,才建议调整为4或8)
* 对比视频分辨率需要相同
反馈和建议
* 微信 : hybase (添加时请注明 hysVideoQC 或 vmaf)
* Email : hybase@qq.com
* QQ : 23207689
代码及项目官方地址
* ffmpeg: https://www.ffmpeg.org
* vmaf : https://github.com/Netflix/vmaf (VMAF 的全称是:Visual Multimethod Assessment Fusion,视频质量多方法评价融合。这项技术是由美国Netflix公司开发的一套主观视频质量评价体系。)更多信息请参考官方介绍的论文(百度盘中有2022.8.17的 vmaf-2.3.1 版本代码,解压后vmaf-2.3.1\resource\doc\papers 目录中有论文的pdf文档)
* QT镜像: https://mirrors.tuna.tsinghua.edu.cn/qt/official_releases/qt/