hysVideoQC v0.0.2.002版本发布

概述

hysVideoQC (video quality comparator) 视频质量比较工具

基于开源项目VMAFFFMPEG开发的一款 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
  1. Distortion files (转码后的文件)

    • Support file format same as reference file
    • Maximum support 8 files in one QC task
    • Support file details based on FFMPEG
  2. VMAF options

    • Support PSNR/SSIM/MS_SSIM/VMAF
    • Support JSON/XML/CSV files based on VMAF
    • Support compare part of file if necessary.
  3. 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/
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值