x265探索与研究(十):encodeSlice()函数、encodeCTU()函数、encodeCU()函数与finishCU()函数分析

x265探索与研究(十):encodeSlice()函数、encodeCTU()函数、encodeCU()函数与finishCU()函数分析

 

        encodeSlice()函数、encodeCTU()函数、encodeCU()函数与finishCU()函数都是编码的重要函数,根据函数命名均可得知其各自的功能。下面首先给出这几个函数之间的调用关系图。

 

 

 

1、encodeSlice()函数

 

        encodeSlice()函数位于frameencoder.cpp中,其主要功能就是遍历当前Slice中的CTU,并调用encodeCTU()函数对每个CTU进行进一步地处理,对应的代码分析如下:

 

/*=============================================================*/
/*
 ====== Analysed by: RuiDong Fang
 ====== Csdn Blog:	 http://blog.csdn.net/frd2009041510
 ====== Date:		 2016.04.19
 ====== Funtion:	 encodeSlice()函数
 */
/*=============================================================*/
void FrameEncoder::encodeSlice()
{
    Slice* slice = m_frame->m_encData->m_slice;	//获取当前slice,通常一帧中包括很多Slice,但默认情况一帧即一个Slice
    const uint32_t widthInLCUs = slice->m_sps->numCuInWidth;
//	printf("widthInLCUs=%d",widthInLCUs);
    const uint32_t lastCUAddr = (slice->m_endCUAddr + NUM_4x4_PARTITIONS - 1) / NUM_4x4_PARTITIONS;
    const uint32_t numSubstreams = m_param->bEnableWavefront ? slice->m_sps->numCuInHeight : 1;

    SAOParam* saoParam = slice->m_sps->bUseSAO ? m_frame->m_encData->m_saoParam : NULL;	//saoParam

	//遍历当前Slice中的CU
	for (uint32_t cuAddr = 0; cuAddr < lastCUAddr; cuAddr++)
    {
        uint32_t col = cuAddr % widthInLCUs;
        uint32_t lin = cuAddr / widthInLCUs;
        uint32_t subStrm = lin % numSubstreams;
        CUData* ctu = m_frame->m_encData->getPicCTU(cuAddr);	//获取CTU

        m_entropyCoder.setBitstream(&m_outStreams[subStrm]);

        // Synchronize cabac probabilities with upper-right CTU if it's available and we're at the start of a line.
        if (m_param->bEnableWavefront && !col && lin)
        {
            m_entropyCoder.copyState(m_initSliceContext);
            m_entropyCoder.loadContexts(m_rows[lin - 1].bufferedEntropy);
        }

		//若saoParam
        if (saoParam)
        {
            if (saoParam->bSaoFlag[0] || saoParam->bSaoFlag[1])
            {
                int mergeLeft = col && saoParam->ctuParam[0][cuAddr].mergeMode == SAO_MERGE_LEFT;
                int mergeUp = lin && saoParam->ctuParam[0][cuAddr].mergeMode == SAO_MERGE_UP;
                if (col)
                    m_entropyCoder.codeSaoMerge(mergeLeft);
                if (lin && !mergeLeft)
                    m_entropyCoder.codeSaoMerge(mergeUp);
                if (!mergeLeft && !mergeUp)
                {
                    if (saoParam->bSaoFlag[0])
                        m_entropyCoder.codeSaoOffset(saoParam->ctuParam[0][cuAddr], 0);
                    if (saoParam->bSaoFlag[1])
                    {
                        m_entropyCoder.codeSaoOffset(saoParam->ctuParam[1][cuAddr], 1);
                        m_entropyCoder.codeSaoOffset(saoParam->ctuParam[2][cuAddr], 2);
                    }
                }
            }
            else
            {
                for (int i = 0; i < 3; i++)
                    saoParam->ctuParam[i][cuAddr].reset();
            }
        }

        // final coding (bitstream generation) for this CU滤波过后的熵编码处理
        m_entropyCoder.encodeCTU(*ctu, m_cuGeoms[m_ctuGeomMap[cuAddr]]);	//=====================调用encodeCTU()

        if (m_param->bEnableWavefront)	//若使能波前
        {
            if (col == 1)
                // Store probabilities of second CTU in line into buffer
                m_rows[lin].bufferedEntropy.loadContexts(m_entropyCoder);

            if (col == widthInLCUs - 1)
                m_entropyCoder.finishSlice();
        }
    }
    if (!m_param->bEnableWavefront)	//若不使能波前
        m_entropyCoder.finishSlice();
}

 

2、encodeCTU()函数

 

        encodeCTU()函数位于entropy.cpp中,其主要功能就是对CTU中包含的每个CU调用encodeCU()函数进行进一步地处理,对应的代码分析如下:

 

void Entropy::encodeCTU(const CUData& ctu, const CUGeom& cuGeom)
{
    bool bEncodeDQP = ctu.m_slice->m_pps->bUseDQP;
    encodeCU(ctu, cuGeom, 0, 0, bEncodeDQP);	//===============================调用encodeCU(),即开始编码一个CTU,深度为0
}

 

3、encodeCU()函数

 

        encodeCU()函数位于entropy.cpp中,其主要功能包括CU的继续分割及其递归调用、对预测信息、编码系数与预测模式等的编码,并最后调用finishCU()函数完成比特流的写入,对应的代码分析如下:

 

/* encode a CU block recursively */
void Entropy::encodeCU(const CUData& ctu, const CUGeom& cuGeom, uint32_t absPartIdx, uint32_t depth, bool& bEncodeDQP)
{
    const Slice* slice = ctu.m_slice;

    int cuSplitFlag = !(cuGeom.flags & CUGeom::LEAF);	//CU分割标志
    int cuUnsplitFlag = !(cuGeom.flags & CUGeom::SPLIT_MANDATORY);	//CU不分割标志

	//CU不继续分割时
    if (!cuUnsplitFlag)
    {
        uint32_t qNumParts = cuGeom.numPartitions >> 2;
        if (depth == slice->m_pps->maxCuDQPDepth && slice->m_pps->bUseDQP)	//获取深度
            bEncodeDQP = true;
        for (uint32_t qIdx = 0; qIdx < 4; ++qIdx, absPartIdx += qNumParts)	//当前深度的4个CU
        {
            const CUGeom& childGeom = *(&cuGeom + cuGeom.childOffset + qIdx);
            if (childGeom.flags & CUGeom::PRESENT)
                encodeCU(ctu, childGeom, absPartIdx, depth + 1, bEncodeDQP);	//递归调用
        }
        return;
    }

	//CU继续分割时
    if (cuSplitFlag)
        codeSplitFlag(ctu, absPartIdx, depth);	//===============================编码分割标志(此处包含CU的继续分割过程)

    if (depth < ctu.m_cuDepth[absPartIdx] && depth < g_maxCUDepth)	//分割后的深度若符合要求,则递归调用
    {
        uint32_t qNumParts = cuGeom.numPartitions >> 2;
        if (depth == slice->m_pps->maxCuDQPDepth && slice->m_pps->bUseDQP)	//获取深度,由于继续分割,此处的深度应该比之前的深度+1
            bEncodeDQP = true;
        for (uint32_t qIdx = 0; qIdx < 4; ++qIdx, absPartIdx += qNumParts)	//分割成的4个更小CU,即分割后的4个CU
        {
            const CUGeom& childGeom = *(&cuGeom + cuGeom.childOffset + qIdx);
            encodeCU(ctu, childGeom, absPartIdx, depth + 1, bEncodeDQP);	//递归调用
        }
        return;
    }

    if (depth <= slice->m_pps->maxCuDQPDepth && slice->m_pps->bUseDQP)
        bEncodeDQP = true;

    if (slice->m_pps->bTransquantBypassEnabled)
        codeCUTransquantBypassFlag(ctu.m_tqBypass[absPartIdx]);	//===============================编码忽略的CU变换、量化的标志

    if (!slice->isIntra())	//若不为帧内,即不是I帧
    {
        codeSkipFlag(ctu, absPartIdx);	//===============================编码Skip标志
        if (ctu.isSkipped(absPartIdx))	//若为Skip模式
        {
            codeMergeIndex(ctu, absPartIdx);	//===============================编码合并索引
            finishCU(ctu, absPartIdx, depth, bEncodeDQP);	//===============================调用finishCU(),完成Bit的最终写入
            return;
        }
        codePredMode(ctu.m_predMode[absPartIdx]);	//===============================编码预测模式
    }

    codePartSize(ctu, absPartIdx, depth);	//===============================编码分割大小

    // prediction Info ( Intra : direction mode, Inter : Mv, reference idx )
    codePredInfo(ctu, absPartIdx);	//===============================编码预测信息

    uint32_t tuDepthRange[2];
    if (ctu.isIntra(absPartIdx))
        ctu.getIntraTUQtDepthRange(tuDepthRange, absPartIdx);
    else
        ctu.getInterTUQtDepthRange(tuDepthRange, absPartIdx);

    // Encode Coefficients, allow codeCoeff() to modify bEncodeDQP
    codeCoeff(ctu, absPartIdx, bEncodeDQP, tuDepthRange);	//===============================编码系数

    // --- write terminating bit ---
    finishCU(ctu, absPartIdx, depth, bEncodeDQP);	//===============================调用finishCU(),完成Bit的最终写入
}

 

4、finishCU()函数

 

        finishCU()函数位于entropy.cpp中,其主要功能是完成比特流的写入及其临界条件的判断等,对应的代码分析如下:

 

/* finish encoding a cu and handle end-of-slice conditions */
void Entropy::finishCU(const CUData& ctu, uint32_t absPartIdx, uint32_t depth, bool bCodeDQP)
{
    const Slice* slice = ctu.m_slice;
    uint32_t realEndAddress = slice->m_endCUAddr;	//真正的结束地址
    uint32_t cuAddr = ctu.getSCUAddr() + absPartIdx;	//CU的地址
    X265_CHECK(realEndAddress == slice->realEndAddress(slice->m_endCUAddr), "real end address expected\n");

    uint32_t granularityMask = g_maxCUSize - 1;
    uint32_t cuSize = 1 << ctu.m_log2CUSize[absPartIdx];
    uint32_t rpelx = ctu.m_cuPelX + g_zscanToPelX[absPartIdx] + cuSize;
    uint32_t bpely = ctu.m_cuPelY + g_zscanToPelY[absPartIdx] + cuSize;
    bool granularityBoundary = (((rpelx & granularityMask) == 0 || (rpelx == slice->m_sps->picWidthInLumaSamples )) &&
                                ((bpely & granularityMask) == 0 || (bpely == slice->m_sps->picHeightInLumaSamples)));

    if (slice->m_pps->bUseDQP)
        const_cast<CUData&>(ctu).setQPSubParts(bCodeDQP ? ctu.getRefQP(absPartIdx) : ctu.m_qp[absPartIdx], absPartIdx, depth);

    if (granularityBoundary)
    {
        // Encode slice finish
        bool bTerminateSlice = false;	//初始化bTerminateSlice为false
        if (cuAddr + (NUM_4x4_PARTITIONS >> (depth << 1)) == realEndAddress)	//若条件成立,则Slice结束
            bTerminateSlice = true;

        // The 1-terminating bit is added to all streams, so don't add it here when it's 1.
        if (!bTerminateSlice)
            encodeBinTrm(0);	//======================Encode terminating bin

        if (!m_bitIf)
            resetBits(); // TODO: most likely unnecessary
    }
}

 

时间: 2024-10-31 10:15:39

x265探索与研究(十):encodeSlice()函数、encodeCTU()函数、encodeCU()函数与finishCU()函数分析的相关文章

x265探索与研究(九):compressFrame()函数

x265探索与研究(九):compressFrame()函数           compressFrame()函数是一个功能繁杂且分析难度较大的函数,主要包括时间戳的初始化工作.access unit的设计.加权预测技术.运动参考帧的估计.当前Slice的QP值确定.熵编码相关信息配置.并行计算与否及其空间的申请.SEI相关配置.线程控制.CTU分析.Multi-pass Encoding.滤波与去噪处理等等,其中最重要的就是调用了encodeSlice()函数.           comp

x265探索与研究(七):encode()函数

x265探索与研究(七):encode()函数           在x265中,main()函数中调用了encoder_encode()函数,而encoder_encode()函数调用了encode()函数,encode()函数的主要功能是输入一帧图像,得到一帧图像的输出.           encode()函数主要包括大致三个部分: (1)分析是否由于错误造成的代码终止,如g_checkFailures.m_aborted. (2)判断是否有输入帧,若有,则判断该输入帧的像素深度.颜色空间

x265探索与研究(六):main()函数

x265探索与研究(六):main()函数           x265源码的入口函数是main(),本文分析main()的主要功能.首先给出main()函数的功能及其代码结构:其次给出main()函数源码以及分析:最后给出main()函数中的主要功能函数的具体功能.   1.main()函数的功能及其代码结构           main()函数的主要功能是解析参数并进行编码的一些准备工作,调用了如下几个重要的函数: (1)cliopt.parse()函数:解析参数 (2)api->encod

x265探索与研究(八):x265中的并行处理机制函数关系分析

x265探索与研究(八):x265中的并行处理机制函数关系分析           HEVC的高计算复杂度如果仅仅依赖于单核处理器计算能力的提高,其代价是非常昂贵的,为此,HEVC的设计充分考虑到了并行计算的需求.x265不同于HM(HEVC test Model),x265将并行计算发挥地更加淋淋尽致.在main()函数中,为了完成多线程计算,读完24帧输入帧后才开始编码的原因也基于此.           为了理清x265中的并行处理机制,首先给出了如下图的并行处理函数关系图:      

x265探索与研究(一):x265下载安装与配置

x265下载安装与配置         研究了这么久的HEVC Test Model(HM),相信大家对x265开源代码的实现与框架早就充满了好奇,接下来的日子,我将把自己入手学习与探索"x265开源代码的实现与框架"的过程记录下来,与大家共同进步学习. 1.x265下载地址与参考资料 x265的官网为: http://x265.org/ x265下载地址: https://bitbucket.org/multicoreware/x265/downloads 或 http://ftp.

x265探索与研究(五):如何用VS调试x265?

如何用VS调试x265? 1.设置cli为启动项         用VS打开工程项目,如下图:         右击cli,设置为启动项,如下图: 2.配置路径和命令行参数         右击cli选择Properties,如下图所示         进入如下界面:         将Command Arguments和Working Directory中的内容填写好即可.         示例如下: Command Arguments: --preset fast --input hall_

x265探索与研究(二):x265使用基本方法

x265使用基本方法         首先,完成x265的下载安装与配置. (参考网址:http://blog.csdn.net/frd2009041510/article/details/50446007) 接下来介绍x265编解码视频的基本方法. 第一步:         进入"...x265_1.8\build\vc10-x86",双击"build-all.bat",则进行编译.文件夹中的内容变化如下两图所示.      第二步:         用VS打开上

x265探索与研究(三):如何播放*.265格式的视频或解码视频流

如何播放*.265格式的视频或解码视频流               如下图,在得到.265格式的视频或视频流后应如何播放265格式的视频呢?本博文总结出5种播放265格式视频或视频流的基本方法. 方式一:Elecard HEVC Player Sample           软件下载地址:http://download.csdn.net/detail/frd2009041510/9387068         下载后直接双击安装即可,播放效果如下图所示. 方式二:GitlHEVCAnalyz

x265探索与研究(四):如何编码视频?

如何编码视频?           本文介绍x265中的两种编码视频方法,一是采用命令行的方法:二是GUI的方式.   1.命令行的方法   (1).第一种方式生成*.265格式的视频文件           第一种方式可以生成*.265格式的视频文件,对应的命令为: x265 --input-res 352x288 --fps 30 hall_cif_352x288_300.yuv -o hall_cif_352x288_300.h265   (2).第二种方式可以生成*.bin格式的视频流文