【x265编码器】章节2——x265编码流程及基于x265的编码器demo

  系列文章目录

   HEVC视频编解码标准简介

【x264编码器】章节1——x264编码流程及基于x264的编码器demo

【x264编码器】章节2——x264的lookahead流程分析

【x264编码器】章节3——x264的码率控制

【x264编码器】章节4——x264的帧内预测流程

【x264编码器】章节5——x264的帧间预测流程

【x264编码器】章节6——x264的变换量化

【x265编码器】章节1——lookahead模块分析

【x265编码器】章节2——编码流程及基于x265的编码器demo

【x265编码器】章节3——帧内预测流程

【x265编码器】章节4——帧间预测流程

【x265编码器】章节5——x265帧间运动估计流程

【x265编码器】章节6——x265的码率控制

【x265编码器】章节7——滤波模块

【x265编码器】章节8——变换量化模块


目录

一、编码流程

二、基于x265的视频编码器代码demo

三、各个模块分析

1.编码器预设参数 x265_param_default_preset

2.打开编码器 x265_encoder_open

3.编码 x265_encoder_encode

3.1 FrameEncoder::startCompressFrame

3.2 FrameEncoder::threadMain()

3.3 FrameEncoder::getEncodedPicture()

4.关闭编码器 x265_encoder_close


一、编码流程

如图是x265编码器编码的总体流程:

x265完整的流程框架如下:

二、基于x265的视频编码器代码demo

下面梳理了x265编码的总体流程,希望对读者有所帮助,这边根据上面的理解,并且补充了一份自己的demo,完整的工程在基于x265的视频编码器demo

#include <stdio.h>
#include <stdlib.h>
#include <x265.h>

int main(int argc, char** argv){
    int ret;
    FILE* fp_input = NULL;
    FILE* fp_output = NULL;  
    x265_param* p_x265_param = x265_param_alloc();
    //1.编码器参数初始化
    x265_param_default_preset(p_x265_param, "ultafast", "zerolatecy"); 
    p_x265_param->sourceWidth = 480;
    p_x265_param->sourceHeight = 360;
    //编码帧率
    p_x265_param->fpsNum = 25;      
    p_x265_param->fpsDenom = 1;
    //write sps,pps 写在 keyframe之前
    p_x265_param->bRepeatHeaders = 1;
    p_x265_param->internalCsp = X265_CSP_I420;
    x265_param_apply_profile(p_x265_param, x265_profile_names[0]);
    //输入YUV
    fp_input = fopen("./input_480x360.yuv", "rb");
    //输出地址
    fp_output = fopen("./output.h265", "wb");
    if(fp_input==NULL||fp_output==NULL)
    {
        return -1;
    }
    //2.打开编码器
    x265_encoder* p_encoder=NULL;
    p_encoder = x265_encoder_open(p_x265_param);
    if(p_encoder==NULL){
        printf("x265_encoder_open fail\n");
        return 0;
    }
    //3.填写输入图片信息
    x265_picture *p_in_pic=NULL;
    p_in_pic = x265_picture_alloc();
    x265_picture_init(p_x265_param, p_in_pic);
    int frame_pix = p_x265_param->sourceWidth * p_x265_param->sourceHeight;
    char* buff=(char *)malloc(frame_pix * 3 / 2);
    p_in_pic->planes[0]=buff; 
    p_in_pic->planes[1]=buff + frame_pix;
    p_in_pic->planes[2]=buff + frame_pix * 5 / 4;
    p_in_pic->stride[0]=p_x265_param->sourceWidth;
    p_in_pic->stride[1]=p_x265_param->sourceWidth / 2;
    p_in_pic->stride[2]=p_x265_param->sourceWidth / 2;
    //计算输入yuv的总数量
    int frame_num = 0;
    fseek(fp_input, 0, SEEK_END);
    frame_num = ftell(fp_input)/(frame_pix * 3 / 2);
    fseek(fp_input,0,SEEK_SET);
    //4.进行编码
    x265_nal* p_nals=NULL;
    uint32_t nal_num = 0;
    for(int i = 0;i < frame_num; i++)
    {
        fread(p_in_pic->planes[0], 1, frame_pix,fp_input);    //Y
        fread(p_in_pic->planes[1], 1, frame_pix/4,fp_input);  //U
        fread(p_in_pic->planes[2], 1, frame_pix/4,fp_input);  //V
        ret = x265_encoder_encode(p_encoder, &p_nals, &nal_num, p_in_pic, NULL); 
        printf("encode %d frame2\n",i);
        for(int j = 0; j < nal_num;j++)
        {
            fwrite(p_nals[j].payload, 1, p_nals[j].sizeBytes, fp_output);
        }
    }
    while(1)
    {
        ret=x265_encoder_encode(p_encoder, &p_nals, &nal_num, NULL, NULL);
        if(ret==0)
        {
            break;
        }
        printf("Flush 1 frame.\n");

        for(int j=0; j<nal_num; j++)
        {
            fwrite(p_nals[j].payload, 1, p_nals[j].sizeBytes, fp_output);
        }
    }
    //5.释放内存,关闭编码器
    fclose(fp_input);
    fclose(fp_output);
    x265_encoder_close(p_encoder);
    x265_picture_free(p_in_pic);
    x265_param_free(p_x265_param);
    free(buff);
    return 0;
}

三、各个模块分析

1.编码器预设参数 x265_param_default_preset

265_param_default_preset 的函数,它根据指定的预设和调整参数,设置了一个 x265_param 结构体的默认值,并根据preset和tune设置不同的配置参数,其中preset可选:

  • ultrafast:超快速预设
  • superfast:超快预设
  • veryfast:非常快预设
  • faster:更快预设
  • fast:快速预设
  • medium:中等预设
  • slow:慢预设
  • slower:更慢预设
  • veryslow:非常慢预设
  • placebo:安慰剂预设(最慢)

psnr可选:psnr、ssim、fastdecode、fast-decode、zerolatency、zero-latency、grain、animation和vmaf:

int x265_param_default_preset(x265_param* param, const char* preset, const char* tune)
{
#if EXPORT_C_API
    ::x265_param_default(param);
#else
    X265_NS::x265_param_default(param);
#endif

    if (preset)
    {
        char *end;
        int i = strtol(preset, &end, 10);
        if (*end == 0 && i >= 0 && i < (int)(sizeof(x265_preset_names) / sizeof(*x265_preset_names) - 1))
            preset = x265_preset_names[i];

        if (!strcmp(preset, "ultrafast"))
        {
            param->maxNumMergeCand = 2;
            param->bIntraInBFrames = 0;
            param->lookaheadDepth = 5;
            param->scenecutThreshold = 0; // disable lookahead
            param->maxCUSize = 32;
            param->minCUSize = 16;
            param->bframes = 3;
            param->bFrameAdaptive = 0;
            param->subpelRefine = 0;
            param->searchMethod = X265_DIA_SEARCH;
            param->bEnableSAO = 0;
            param->bEnableSignHiding = 0;
            param->bEnableWeightedPred = 0;
            param->rdLevel = 2;
            param->maxNumReferences = 1;
            param->limitReferences = 0;
            param->rc.aqStrength = 0.0;
            param->rc.aqMode = X265_AQ_NONE;
            param->rc.hevcAq = 0;
            param->rc.qgSize = 32;
            param->bEnableFastIntra = 1;
        }
        else if (!strcmp(preset, "superfast"))
        {
            param->maxNumMergeCand = 2;
            param->bIntraInBFrames = 0;
            param->lookaheadDepth = 10;
            param->maxCUSize = 32;
            param->bframes = 3;
            param->bFrameAdaptive = 0;
            param->subpelRefine = 1;
            param->bEnableWeightedPred = 0;
            param->rdLevel = 2;
            param->maxNumReferences = 1;
            param->limitReferences = 0;
            param->rc.aqStrength = 0.0;
            param->rc.aqMode = X265_AQ_NONE;
            param->rc.hevcAq = 0;
            param->rc.qgSize = 32;
            param->bEnableSAO = 0;
            param->bEnableFastIntra = 1;
        }
        else if (!strcmp(preset, "veryfast"))
        {
            param->maxNumMergeCand = 2;
            param->limitReferences = 3;
            param->bIntraInBFrames = 0;
            param->lookaheadDepth = 15;
            param->bFrameAdaptive = 0;
            param->subpelRefine = 1;
            param->rdLevel = 2;
            param->maxNumReferences = 2;
            param->rc.qgSize = 32;
            param->bEnableFastIntra = 1;
        }
        else if (!strcmp(preset, "faster"))
        {
            param->maxNumMergeCand = 2;
            param->limitReferences = 3;
            param->bIntraInBFrames = 0;
            param->lookaheadDepth = 15;
            param->bFrameAdaptive = 0;
            param->rdLevel = 2;
            param->maxNumReferences = 2;
            param->bEnableFastIntra = 1;
        }
        else if (!strcmp(preset, "fast"))
        {
            param->maxNumMergeCand = 2;
            param->limitReferences = 3;
            param->bEnableEarlySkip = 0;
            param->bIntraInBFrames = 0;
            param->lookaheadDepth = 15;
            param->bFrameAdaptive = 0;
            param->rdLevel = 2;
            param->maxNumReferences = 3;
            param->bEnableFastIntra = 1;
        }
        else if (!strcmp(preset, "medium"))
        {
            /* defaults */
        }
        else if (!strcmp(preset, "slow"))
        {
            param->limitReferences = 3;
            param->bEnableEarlySkip = 0;
            param->bIntraInBFrames = 0;
            param->bEnableRectInter = 1;
            param->lookaheadDepth = 25;
            param->rdLevel = 4;
            param->rdoqLevel = 2;
            param->psyRdoq = 1.0;
            param->subpelRefine = 3;
            param->searchMethod = X265_STAR_SEARCH;
            param->maxNumReferences = 4;
            param->limitModes = 1;
            param->lookaheadSlices = 4; // limit parallelism as already enough work exists
        }
        else if (!strcmp(preset, "slower"))
        {
            param->bEnableEarlySkip = 0;
            param->bEnableWeightedBiPred = 1;
            param->bEnableAMP = 1;
            param->bEnableRectInter = 1;
            param->lookaheadDepth = 40;
            param->bframes = 8;
            param->tuQTMaxInterDepth = 3;
            param->tuQTMaxIntraDepth = 3;
            param->rdLevel = 6;
            param->rdoqLevel = 2;
            param->psyRdoq = 1.0;
            param->subpelRefine = 4;
            param->maxNumMergeCand = 4;
            param->searchMethod = X265_STAR_SEARCH;
            param->maxNumReferences = 5;
            param->limitModes = 1;
            param->lookaheadSlices = 0; // disabled for best quality
            param->limitTU = 4;
        }
        else if (!strcmp(preset, "veryslow"))
        {
            param->bEnableEarlySkip = 0;
            param->bEnableWeightedBiPred = 1;
            param->bEnableAMP = 1;
            param->bEnableRectInter = 1;
            param->lookaheadDepth = 40;
            param->bframes = 8;
            param->tuQTMaxInterDepth = 3;
            param->tuQTMaxIntraDepth = 3;
            param->rdLevel = 6;
            param->rdoqLevel = 2;
            param->psyRdoq = 1.0;
            param->subpelRefine = 4;
            param->maxNumMergeCand = 5;
            param->searchMethod = X265_STAR_SEARCH;
            param->maxNumReferences = 5;
            param->limitReferences = 0;
            param->limitModes = 0;
            param->lookaheadSlices = 0; // disabled for best quality
            param->limitTU = 0;
        }
        else if (!strcmp(preset, "placebo"))
        {
            param->bEnableEarlySkip = 0;
            param->bEnableWeightedBiPred = 1;
            param->bEnableAMP = 1;
            param->bEnableRectInter = 1;
            param->lookaheadDepth = 60;
            param->searchRange = 92;
            param->bframes = 8;
            param->tuQTMaxInterDepth = 4;
            param->tuQTMaxIntraDepth = 4;
            param->rdLevel = 6;
            param->rdoqLevel = 2;
            param->psyRdoq = 1.0;
            param->subpelRefine = 5;
            param->maxNumMergeCand = 5;
            param->searchMethod = X265_STAR_SEARCH;
            param->bEnableTransformSkip = 1;
            param->recursionSkipMode = 0;
            param->maxNumReferences = 5;
            param->limitReferences = 0;
            param->lookaheadSlices = 0; // disabled for best quality
            // TODO: optimized esa
        }
        else
            return -1;
    }
    if (tune)
    {
        if (!strcmp(tune, "psnr"))
        {
            param->rc.aqStrength = 0.0;
            param->psyRd = 0.0;
            param->psyRdoq = 0.0;
        }
        else if (!strcmp(tune, "ssim"))
        {
            param->rc.aqMode = X265_AQ_AUTO_VARIANCE;
            param->psyRd = 0.0;
            param->psyRdoq = 0.0;
        }
        else if (!strcmp(tune, "fastdecode") ||
                 !strcmp(tune, "fast-decode"))
        {
            param->bEnableLoopFilter = 0;
            param->bEnableSAO = 0;
            param->bEnableWeightedPred = 0;
            param->bEnableWeightedBiPred = 0;
            param->bIntraInBFrames = 0;
        }
        else if (!strcmp(tune, "zerolatency") ||
                 !strcmp(tune, "zero-latency"))
        {
            param->bFrameAdaptive = 0;
            param->bframes = 0;
            param->lookaheadDepth = 0;
            param->scenecutThreshold = 0;
            param->bHistBasedSceneCut = 0;
            param->rc.cuTree = 0;
            param->frameNumThreads = 1;
        }
        else if (!strcmp(tune, "grain"))
        {
            param->rc.ipFactor = 1.1;
            param->rc.pbFactor = 1.0;
            param->rc.cuTree = 0;
            param->rc.aqMode = 0;
            param->rc.hevcAq = 0;
            param->rc.qpStep = 1;
            param->rc.bEnableGrain = 1;
            param->recursionSkipMode = 0;
            param->psyRd = 4.0;
            param->psyRdoq = 10.0;
            param->bEnableSAO = 0;
            param->rc.bEnableConstVbv = 1;
        }
        else if (!strcmp(tune, "animation"))
        {
            param->bframes = (param->bframes + 2) >= param->lookaheadDepth? param->bframes : param->bframes + 2;
            param->psyRd = 0.4;
            param->rc.aqStrength = 0.4;
            param->deblockingFilterBetaOffset = 1;
            param->deblockingFilterTCOffset = 1;
        }
        else if (!strcmp(tune, "vmaf"))  /*Adding vmaf for x265 + SVT-HEVC integration support*/
        {
            /*vmaf is under development, currently x265 won't support vmaf*/
        }
        else
            return -1;
    }

#ifdef SVT_HEVC
    if (svt_set_preset(param, preset))
        return -1;
#endif

    return 0;
}

2.打开编码器 x265_encoder_open

函数 x265_encoder_open,用于打开一个 x265 编码器并返回一个指向该编码器的指针:

x265_encoder *x265_encoder_open(x265_param *p)
{
    if (!p)
        return NULL;

#if _MSC_VER
#pragma warning(disable: 4127) // conditional expression is constant, yes I know
#endif
    //代码根据编译选项和配置检查内部位深度是否匹配。如果不匹配,代码会输出错误日志并返回 NULL
#if HIGH_BIT_DEPTH
    if (X265_DEPTH != 10 && X265_DEPTH != 12)
#else
    if (X265_DEPTH != 8)
#endif
    {
        x265_log(p, X265_LOG_ERROR, "Build error, internal bit depth mismatch\n");
        return NULL;
    }
    //代码创建了三个 x265_param 结构体指针 param、latestParam 和 zoneParam,并调用 PARAM_NS::x265_param_default 函数为每个结构体设置默认参数
    Encoder* encoder = NULL;
    x265_param* param = PARAM_NS::x265_param_alloc();
    x265_param* latestParam = PARAM_NS::x265_param_alloc();
    x265_param* zoneParam = PARAM_NS::x265_param_alloc();

    if(param) PARAM_NS::x265_param_default(param);
    if(latestParam) PARAM_NS::x265_param_default(latestParam);
    if(zoneParam) PARAM_NS::x265_param_default(zoneParam);
  
    if (!param || !latestParam || !zoneParam)
        goto fail;
    if (p->rc.zoneCount || p->rc.zonefileCount)
    {
        int zoneCount = p->rc.zonefileCount ? p->rc.zonefileCount : p->rc.zoneCount;
        param->rc.zones = x265_zone_alloc(zoneCount, !!p->rc.zonefileCount);
        latestParam->rc.zones = x265_zone_alloc(zoneCount, !!p->rc.zonefileCount);
        zoneParam->rc.zones = x265_zone_alloc(zoneCount, !!p->rc.zonefileCount);
    }
    //通过调用 x265_copy_params 函数将输入参数 p 的值复制到 param、latestParam 和 zoneParam 结构体中
    x265_copy_params(param, p);
    x265_copy_params(latestParam, p);
    x265_copy_params(zoneParam, p);
    x265_log(param, X265_LOG_INFO, "HEVC encoder version %s\n", PFX(version_str));
    x265_log(param, X265_LOG_INFO, "build info %s\n", PFX(build_info_str));
    //创建一个 Encoder 对象,并将其指针赋值给 encoder
    encoder = new Encoder;

#ifdef SVT_HEVC
    //检查是否启用了 SVT-HEVC 编码
    if (param->bEnableSvtHevc)
    {
        EB_ERRORTYPE return_error = EB_ErrorNone;
        int ret = 0;
        //svt_initialise_app_context 函数初始化应用上下文, svt_initialise_input_buffer 函数初始化输入缓冲区
        svt_initialise_app_context(encoder);
        ret = svt_initialise_input_buffer(encoder);
        if (!ret)
        {
            x265_log(param, X265_LOG_ERROR, "SVT-HEVC Encoder: Unable to allocate input buffer \n");
            goto fail;
        }
        //调用 EbInitHandle 函数创建编码器句柄,并将其赋值给 encoder->m_svtAppData->svtEncoderHandle
        // Create Encoder Handle
        return_error = EbInitHandle(&encoder->m_svtAppData->svtEncoderHandle, encoder->m_svtAppData, encoder->m_svtAppData->svtHevcParams);
        if (return_error != EB_ErrorNone)
        {
            x265_log(param, X265_LOG_ERROR, "SVT-HEVC Encoder: Unable to initialise encoder handle  \n");
            goto fail;
        }
        //复制编码参数
        memcpy(encoder->m_svtAppData->svtHevcParams, param->svtHevcParam, sizeof(EB_H265_ENC_CONFIGURATION));

        // Send over all configuration parameters
        return_error = EbH265EncSetParameter(encoder->m_svtAppData->svtEncoderHandle, encoder->m_svtAppData->svtHevcParams);
        if (return_error != EB_ErrorNone)
        {
            x265_log(param, X265_LOG_ERROR, "SVT-HEVC Encoder: Error while configuring encoder parameters  \n");
            goto fail;
        }

        // Init Encoder 初始化编码器
        return_error = EbInitEncoder(encoder->m_svtAppData->svtEncoderHandle);
        if (return_error != EB_ErrorNone)
        {
            x265_log(param, X265_LOG_ERROR, "SVT-HEVC Encoder: Encoder init failed  \n");
            goto fail;
        }

        memcpy(param->svtHevcParam, encoder->m_svtAppData->svtHevcParams, sizeof(EB_H265_ENC_CONFIGURATION));
        encoder->m_param = param;
        return encoder;
    }
#endif
    //设置编码器使用的底层函数
    x265_setup_primitives(param);

    if (x265_check_params(param))
        goto fail;

    if (!param->rc.bEnableSlowFirstPass)
        PARAM_NS::x265_param_apply_fastfirstpass(param);

    // may change params for auto-detect, etc
    encoder->configure(param);
    if (encoder->m_aborted)
        goto fail;
    // may change rate control and CPB params 检查并设置编码器的级别和相关参数
    if (!enforceLevel(*param, encoder->m_vps))
        goto fail;

    // will detect and set profile/tier/level in VPS
    determineLevel(*param, encoder->m_vps);

    if (!param->bAllowNonConformance && encoder->m_vps.ptl.profileIdc == Profile::NONE)
    {
        x265_log(param, X265_LOG_INFO, "non-conformant bitstreams not allowed (--allow-non-conformance)\n");
        goto fail;
    }
    //创建编码器
    encoder->create();
    p->frameNumThreads = encoder->m_param->frameNumThreads;

    if (!param->bResetZoneConfig)
    {
        param->rc.zones = X265_MALLOC(x265_zone, param->rc.zonefileCount);
        for (int i = 0; i < param->rc.zonefileCount; i++)
        {
            param->rc.zones[i].zoneParam = X265_MALLOC(x265_param, 1);
            memcpy(param->rc.zones[i].zoneParam, param, sizeof(x265_param));
            param->rc.zones[i].relativeComplexity = X265_MALLOC(double, param->reconfigWindowSize);
        }
    }

    memcpy(zoneParam, param, sizeof(x265_param));
    for (int i = 0; i < param->rc.zonefileCount; i++)
    {
        encoder->configureZone(zoneParam, param->rc.zones[i].zoneParam);
    }

    /* Try to open CSV file handle */
    if (encoder->m_param->csvfn)
    {
        encoder->m_param->csvfpt = x265_csvlog_open(encoder->m_param);
        if (!encoder->m_param->csvfpt)
        {
            x265_log(encoder->m_param, X265_LOG_ERROR, "Unable to open CSV log file <%s>, aborting\n", encoder->m_param->csvfn);
            encoder->m_aborted = true;
        }
    }

    encoder->m_latestParam = latestParam;
    x265_copy_params(latestParam, param);
    if (encoder->m_aborted)
        goto fail;
    //输出参数的详细信息
    x265_print_params(param);
    return encoder;

fail:
    delete encoder;
    PARAM_NS::x265_param_free(param);
    PARAM_NS::x265_param_free(latestParam);
    PARAM_NS::x265_param_free(zoneParam);
    return NULL;
}

3.编码 x265_encoder_encode

函数 x265_encoder_encode,用于对图像进行编码,同时会进行lookahead预处理,最后会获取编码:

int x265_encoder_encode(x265_encoder *enc, x265_nal **pp_nal, uint32_t *pi_nal, x265_picture *pic_in, x265_picture *pic_out)
{
    if (!enc)
        return -1;

    Encoder *encoder = static_cast<Encoder*>(enc);
    int numEncoded;

#ifdef SVT_HEVC
    EB_ERRORTYPE return_error;
    if (encoder->m_param->bEnableSvtHevc)
    {
        static unsigned char picSendDone = 0;
        numEncoded = 0;
        static int codedNal = 0, eofReached = 0;
        EB_H265_ENC_CONFIGURATION* svtParam = (EB_H265_ENC_CONFIGURATION*)encoder->m_svtAppData->svtHevcParams;
        if (pic_in)
        {
            if (pic_in->colorSpace == X265_CSP_I420) // SVT-HEVC supports only yuv420p color space
            {
                EB_BUFFERHEADERTYPE *inputPtr = encoder->m_svtAppData->inputPictureBuffer;

                if (pic_in->framesize) inputPtr->nFilledLen = (uint32_t)pic_in->framesize;
                inputPtr->nFlags = 0;
                inputPtr->pts = pic_in->pts;
                inputPtr->dts = pic_in->dts;
                inputPtr->sliceType = EB_INVALID_PICTURE;

                EB_H265_ENC_INPUT *inputData = (EB_H265_ENC_INPUT*) inputPtr->pBuffer;
                inputData->luma = (unsigned char*) pic_in->planes[0];
                inputData->cb = (unsigned char*) pic_in->planes[1];
                inputData->cr = (unsigned char*) pic_in->planes[2];

                inputData->yStride = encoder->m_param->sourceWidth;
                inputData->cbStride = encoder->m_param->sourceWidth >> 1;
                inputData->crStride = encoder->m_param->sourceWidth >> 1;

                inputData->lumaExt = NULL;
                inputData->cbExt = NULL;
                inputData->crExt = NULL;

                if (pic_in->rpu.payloadSize)
                {
                    inputData->dolbyVisionRpu.payload = X265_MALLOC(uint8_t, 1024);
                    memcpy(inputData->dolbyVisionRpu.payload, pic_in->rpu.payload, pic_in->rpu.payloadSize);
                    inputData->dolbyVisionRpu.payloadSize = pic_in->rpu.payloadSize;
                    inputData->dolbyVisionRpu.payloadType = NAL_UNIT_UNSPECIFIED;
                }
                else
                {
                    inputData->dolbyVisionRpu.payload = NULL;
                    inputData->dolbyVisionRpu.payloadSize = 0;
                }

                // Send the picture to the encoder
                return_error = EbH265EncSendPicture(encoder->m_svtAppData->svtEncoderHandle, inputPtr);

                if (return_error != EB_ErrorNone)
                {
                    x265_log(encoder->m_param, X265_LOG_ERROR, "SVT HEVC encoder: Error while encoding \n");
                    numEncoded = -1;
                    goto fail;
                }
            }
            else
            {
                x265_log(encoder->m_param, X265_LOG_ERROR, "SVT HEVC Encoder accepts only yuv420p input \n");
                numEncoded = -1;
                goto fail;
            }
        }
        else if (!picSendDone) //Encoder flush
        {
            picSendDone = 1;
            EB_BUFFERHEADERTYPE inputPtrLast;
            inputPtrLast.nAllocLen = 0;
            inputPtrLast.nFilledLen = 0;
            inputPtrLast.nTickCount = 0;
            inputPtrLast.pAppPrivate = NULL;
            inputPtrLast.nFlags = EB_BUFFERFLAG_EOS;
            inputPtrLast.pBuffer = NULL;

            return_error = EbH265EncSendPicture(encoder->m_svtAppData->svtEncoderHandle, &inputPtrLast);
            if (return_error != EB_ErrorNone)
            {
                x265_log(encoder->m_param, X265_LOG_ERROR, "SVT HEVC encoder: Error while encoding \n");
                numEncoded = -1;
                goto fail;
            }
        }

        if (eofReached && svtParam->codeEosNal == 0 && !codedNal)
        {
            EB_BUFFERHEADERTYPE *outputStreamPtr = 0;
            return_error = EbH265EncEosNal(encoder->m_svtAppData->svtEncoderHandle, &outputStreamPtr);
            if (return_error == EB_ErrorMax)
            {
                x265_log(encoder->m_param, X265_LOG_ERROR, "SVT HEVC encoder: Error while encoding \n");
                numEncoded = -1;
                goto fail;
            }
            if (return_error != EB_NoErrorEmptyQueue)
            {
                if (outputStreamPtr->pBuffer)
                {
                    //Copy data from output packet to NAL
                    encoder->m_nalList.m_nal[0].payload = outputStreamPtr->pBuffer;
                    encoder->m_nalList.m_nal[0].sizeBytes = outputStreamPtr->nFilledLen;
                    encoder->m_svtAppData->byteCount += outputStreamPtr->nFilledLen;
                    *pp_nal = &encoder->m_nalList.m_nal[0];
                    *pi_nal = 1;
                    numEncoded = 0;
                    codedNal = 1;
                    return numEncoded;
                }

                // Release the output buffer
                EbH265ReleaseOutBuffer(&outputStreamPtr);
            }
        }
        else if (eofReached)
        {
            *pi_nal = 0;
            return numEncoded;
        }

        //Receive Packet
        EB_BUFFERHEADERTYPE *outputPtr;
        return_error = EbH265GetPacket(encoder->m_svtAppData->svtEncoderHandle, &outputPtr, picSendDone);
        if (return_error == EB_ErrorMax)
        {
            x265_log(encoder->m_param, X265_LOG_ERROR, "SVT HEVC encoder: Error while encoding \n");
            numEncoded = -1;
            goto fail;
        }

        if (return_error != EB_NoErrorEmptyQueue)
        {
            if (outputPtr->pBuffer)
            {
                //Copy data from output packet to NAL
                encoder->m_nalList.m_nal[0].payload = outputPtr->pBuffer;
                encoder->m_nalList.m_nal[0].sizeBytes = outputPtr->nFilledLen;
                encoder->m_svtAppData->byteCount += outputPtr->nFilledLen;
                encoder->m_svtAppData->outFrameCount++;
                *pp_nal = &encoder->m_nalList.m_nal[0];
                *pi_nal = 1;
                numEncoded = 1;
            }

            eofReached = outputPtr->nFlags & EB_BUFFERFLAG_EOS;

            // Release the output buffer
            EbH265ReleaseOutBuffer(&outputPtr);
        }
        else if (pi_nal)
            *pi_nal = 0;

        pic_out = NULL;

fail:
        if (numEncoded < 0)
            encoder->m_aborted = true;

        return numEncoded;
    }
#endif

    // While flushing, we cannot return 0 until the entire stream is flushed
    do
    {
        numEncoded = encoder->encode(pic_in, pic_out);
    }
    while ((numEncoded == 0 && !pic_in && encoder->m_numDelayedPic && !encoder->m_latestParam->forceFlush) && !encoder->m_externalFlush);
    if (numEncoded)
        encoder->m_externalFlush = false;
    //如果传入的 pic_in 非空,则将 pic_in 中的 analysisData buffers 设置为 NULL,表示这些缓冲区现在由编码器拥有
    // do not allow reuse of these buffers for more than one picture. The
    // encoder now owns these analysisData buffers.
    if (pic_in)
    {
        pic_in->analysisData.wt = NULL;
        pic_in->analysisData.intraData = NULL;
        pic_in->analysisData.interData = NULL;
        pic_in->analysisData.distortionData = NULL;
    }

    if (pp_nal && numEncoded > 0 && encoder->m_outputCount >= encoder->m_latestParam->chunkStart)
    {
        *pp_nal = &encoder->m_nalList.m_nal[0];
        if (pi_nal) *pi_nal = encoder->m_nalList.m_numNal;
    }
    else if (pi_nal)
        *pi_nal = 0;

    if (numEncoded && encoder->m_param->csvLogLevel && encoder->m_outputCount >= encoder->m_latestParam->chunkStart)
        x265_csvlog_frame(encoder->m_param, pic_out);

    if (numEncoded < 0)
        encoder->m_aborted = true;

    if ((!encoder->m_numDelayedPic && !numEncoded) && (encoder->m_param->bEnableEndOfSequence || encoder->m_param->bEnableEndOfBitstream))
    {   //如果既没有延迟的图像需要编码,也没有编码输出,生成码流的末尾 NAL 单元
        Bitstream bs;
        encoder->getEndNalUnits(encoder->m_nalList, bs);
        *pp_nal = &encoder->m_nalList.m_nal[0];
        if (pi_nal) *pi_nal = encoder->m_nalList.m_numNal;
    }

    return numEncoded;
}

3.1 FrameEncoder::startCompressFrame

通过m_enable,通知FrameEncoder::threadMain()进行编码:

bool FrameEncoder::startCompressFrame(Frame* curFrame)
{
    m_slicetypeWaitTime = x265_mdate() - m_prevOutputTime;
    m_frame = curFrame;
    m_sliceType = curFrame->m_lowres.sliceType;
    curFrame->m_encData->m_frameEncoderID = m_jpId;
    curFrame->m_encData->m_jobProvider = this;
    curFrame->m_encData->m_slice->m_mref = m_mref;

    if (!m_cuGeoms)
    {
        if (!initializeGeoms())
            return false;
    }
    //通知FrameEncoder::threadMain()进行编码
    m_enable.trigger();
    return true;
}

3.2 FrameEncoder::threadMain()

编码主线程,通过m_enable触发编码,通过m_done与FrameEncoder::getEncodedPicture,通知其编码完成:

void FrameEncoder::threadMain()
{
    THREAD_NAME("Frame", m_jpId);

    if (m_pool)
    {
        m_pool->setCurrentThreadAffinity();

        /* the first FE on each NUMA node is responsible for allocating thread
         * local data for all worker threads in that pool. If WPP is disabled, then
         * each FE also needs a TLD instance */
        if (!m_jpId)
        {
            int numTLD = m_pool->m_numWorkers;
            if (!m_param->bEnableWavefront)
                numTLD += m_pool->m_numProviders;

            m_tld = new ThreadLocalData[numTLD];
            for (int i = 0; i < numTLD; i++)
            {
                m_tld[i].analysis.initSearch(*m_param, m_top->m_scalingList);
                m_tld[i].analysis.create(m_tld);
            }

            for (int i = 0; i < m_pool->m_numProviders; i++)
            {
                if (m_pool->m_jpTable[i]->m_isFrameEncoder) /* ugh; over-allocation and other issues here */
                {
                    FrameEncoder *peer = dynamic_cast<FrameEncoder*>(m_pool->m_jpTable[i]);
                    peer->m_tld = m_tld;
                }
            }
        }

        if (m_param->bEnableWavefront)
            m_localTldIdx = -1; // cause exception if used
        else
            m_localTldIdx = m_pool->m_numWorkers + m_jpId;
    }
    else
    {
        m_tld = new ThreadLocalData;
        m_tld->analysis.initSearch(*m_param, m_top->m_scalingList);
        m_tld->analysis.create(NULL);
        m_localTldIdx = 0;
    }

    m_done.trigger();     /* signal that thread is initialized */
    //等待FrameEncoder::startCompressFrame触发
    m_enable.wait();      /* Encoder::encode() triggers this event */

    while (m_threadActive)
    {
        if (m_param->bCTUInfo)
        {
            while (!m_frame->m_ctuInfo)
                m_frame->m_copied.wait();
        }
        if ((m_param->bAnalysisType == AVC_INFO) && !m_param->analysisSave && !m_param->analysisLoad && !(IS_X265_TYPE_I(m_frame->m_lowres.sliceType)))
        {
            while (((m_frame->m_analysisData.interData == NULL && m_frame->m_analysisData.intraData == NULL) || (uint32_t)m_frame->m_poc != m_frame->m_analysisData.poc))
                m_frame->m_copyMVType.wait();
        }
        compressFrame();
        //等待FrameEncoder::getEncodedPicture获取编码结果
        m_done.trigger(); /* FrameEncoder::getEncodedPicture() blocks for this event */
        m_enable.wait();
    }
}

3.3 FrameEncoder::getEncodedPicture()

等待编码主线程FrameEncoder::threadMain编码完成,通知其获取编码结果:

Frame *FrameEncoder::getEncodedPicture(NALList& output)
{
    if (m_frame)
    {   //等待FrameEncoder::threadMain线程通知编码完成
        /* block here until worker thread completes */
        m_done.wait();

        Frame *ret = m_frame;
        m_frame = NULL;
        output.takeContents(m_nalList);
        m_prevOutputTime = x265_mdate();
        return ret;
    }

    return NULL;
}

4.关闭编码器 x265_encoder_close

关闭x265编码器:

void x265_encoder_close(x265_encoder *enc)
{
    if (enc)
    {
        Encoder *encoder = static_cast<Encoder*>(enc);

#ifdef SVT_HEVC
        if (encoder->m_param->bEnableSvtHevc)
        {
            EB_ERRORTYPE return_value;
            return_value = EbDeinitEncoder(encoder->m_svtAppData->svtEncoderHandle);
            if (return_value != EB_ErrorNone)
            {
                x265_log(encoder->m_param, X265_LOG_ERROR, "SVT HEVC encoder: Error while closing the encoder \n");
            }
            return_value = EbDeinitHandle(encoder->m_svtAppData->svtEncoderHandle);
            if (return_value != EB_ErrorNone)
            {
                x265_log(encoder->m_param, X265_LOG_ERROR, "SVT HEVC encoder: Error while closing the Handle \n");
            }

            svt_print_summary(enc);
            EB_H265_ENC_INPUT *inputData = (EB_H265_ENC_INPUT*)encoder->m_svtAppData->inputPictureBuffer->pBuffer;
            if (inputData->dolbyVisionRpu.payload) X265_FREE(inputData->dolbyVisionRpu.payload);

            X265_FREE(inputData);
            X265_FREE(encoder->m_svtAppData->inputPictureBuffer);
            X265_FREE(encoder->m_svtAppData->svtHevcParams);
            encoder->stopJobs();
            encoder->destroy();
            delete encoder;
            return;
        }
#endif

        encoder->stopJobs();
        encoder->printSummary();
        encoder->destroy();
        delete encoder;
    }
}

四、完整调用流程

 完整的工程在基于x265的视频编码器demo,完整的API调用如下:

#include <stdio.h>
#include <stdlib.h>
#include <x265.h>

int main(int argc, char** argv){
    int ret;
    FILE* fp_input = NULL;
    FILE* fp_output = NULL;  
    x265_param* p_x265_param = x265_param_alloc();
    //1.编码器参数初始化
    x265_param_default_preset(p_x265_param, "ultafast", "zerolatecy"); 
    p_x265_param->sourceWidth = 480;
    p_x265_param->sourceHeight = 360;
    //编码帧率
    p_x265_param->fpsNum = 25;      
    p_x265_param->fpsDenom = 1;
    //write sps,pps 写在 keyframe之前
    p_x265_param->bRepeatHeaders = 1;
    p_x265_param->internalCsp = X265_CSP_I420;
    x265_param_apply_profile(p_x265_param, x265_profile_names[0]);
    //输入YUV
    fp_input = fopen("./input_480x360.yuv", "rb");
    //输出地址
    fp_output = fopen("./output.h265", "wb");
    if(fp_input==NULL||fp_output==NULL)
    {
        return -1;
    }
    //2.打开编码器
    x265_encoder* p_encoder=NULL;
    p_encoder = x265_encoder_open(p_x265_param);
    if(p_encoder==NULL){
        printf("x265_encoder_open fail\n");
        return 0;
    }
    //3.填写输入图片信息
    x265_picture *p_in_pic=NULL;
    p_in_pic = x265_picture_alloc();
    x265_picture_init(p_x265_param, p_in_pic);
    int frame_pix = p_x265_param->sourceWidth * p_x265_param->sourceHeight;
    char* buff=(char *)malloc(frame_pix * 3 / 2);
    p_in_pic->planes[0]=buff; 
    p_in_pic->planes[1]=buff + frame_pix;
    p_in_pic->planes[2]=buff + frame_pix * 5 / 4;
    p_in_pic->stride[0]=p_x265_param->sourceWidth;
    p_in_pic->stride[1]=p_x265_param->sourceWidth / 2;
    p_in_pic->stride[2]=p_x265_param->sourceWidth / 2;
    //计算输入yuv的总数量
    int frame_num = 0;
    fseek(fp_input, 0, SEEK_END);
    frame_num = ftell(fp_input)/(frame_pix * 3 / 2);
    fseek(fp_input,0,SEEK_SET);
    //4.进行编码
    x265_nal* p_nals=NULL;
    uint32_t nal_num = 0;
    for(int i = 0;i < frame_num; i++)
    {
        fread(p_in_pic->planes[0], 1, frame_pix,fp_input);    //Y
        fread(p_in_pic->planes[1], 1, frame_pix/4,fp_input);  //U
        fread(p_in_pic->planes[2], 1, frame_pix/4,fp_input);  //V
        ret = x265_encoder_encode(p_encoder, &p_nals, &nal_num, p_in_pic, NULL); 
        printf("encode %d frame2\n",i);
        for(int j = 0; j < nal_num;j++)
        {
            fwrite(p_nals[j].payload, 1, p_nals[j].sizeBytes, fp_output);
        }
    }
    while(1)
    {
        ret=x265_encoder_encode(p_encoder, &p_nals, &nal_num, NULL, NULL);
        if(ret==0)
        {
            break;
        }
        printf("Flush 1 frame.\n");

        for(int j=0; j<nal_num; j++)
        {
            fwrite(p_nals[j].payload, 1, p_nals[j].sizeBytes, fp_output);
        }
    }
    //5.释放内存,关闭编码器
    fclose(fp_input);
    fclose(fp_output);
    x265_encoder_close(p_encoder);
    x265_picture_free(p_in_pic);
    x265_param_free(p_x265_param);
    free(buff);
    return 0;
}

点赞、收藏,会是我继续写作的动力!赠人玫瑰,手有余香。

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值