系列文章目录
【x264编码器】章节1——x264编码流程及基于x264的编码器demo
【x264编码器】章节2——x264的lookahead流程分析
【x265编码器】章节2——编码流程及基于x265的编码器demo
目录
1.编码器预设参数 x265_param_default_preset
3.1 FrameEncoder::startCompressFrame
3.2 FrameEncoder::threadMain()
3.3 FrameEncoder::getEncodedPicture()
一、编码流程
如图是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;
}
点赞、收藏,会是我继续写作的动力!赠人玫瑰,手有余香。