openh264
- OpenH264 是一个开源的 H.264 编码解码库,专为实时应用如 WebRTC 设计。
- OpenH264 编码器因其高效性能、广泛的操作系统和架构支持以及灵活的编码参数设置,成为许多开发者在需要 H.264 编码解码解决方案时的理想选择。
- 关于其介绍可以参考:OpenH264 编解码器介绍。
openh264 主体框架
- 通过 openh264 命令行工具来分析 open264 的主体框架,具体如下图:
- 说明:
从上图可以看出,完成openh264 编码主要分为三个模块,创建编码器、编码过程、销毁编码器。
核心功能介绍
创建编码器
CreateSVCEncHandle
函数
- 该函数创建编码实例ISVCEncoder,函数定义如下,可以看到,内部就是调用 api
函数WelsCreateSVCEncoder完成编码实例创建。
int32_t CreateSVCEncHandle (ISVCEncoder** ppEncoder) {
int32_t ret = 0;
ret = WelsCreateSVCEncoder (ppEncoder);
return ret;
}
WelsCreateSVCEncoder
函数
- 该函数作为 api 函数,在内部完成 new 编码器类CWelsH264SVCEncoder的功能,函数定义如下,因此可以看到CWelsH264SVCEncoder类是进行编码类,继承于ISVCEncoder类。
int32_t WelsCreateSVCEncoder (ISVCEncoder** ppEncoder) {
if ((*ppEncoder = new CWelsH264SVCEncoder()) != NULL) {
return 0;
}
return 1;
}
编码过程
ProcessEncoding
函数
- 完成编码的主要工作,包括参数设置、解析命令行、解析 config 文件、初始化编码器、核心编码等模块;具体如函数定义。
int ProcessEncoding (ISVCEncoder* pPtrEnc, int argc, char** argv, bool bConfigFile) {
int iRet = 0;
if (pPtrEnc == NULL)
return 1;
SFrameBSInfo sFbi;
SEncParamExt sSvcParam;
int64_t iStart = 0, iTotal = 0;
// Preparing encoding process
FILE* pFileYUV = NULL;
int32_t iActualFrameEncodedCount = 0;
int32_t iFrameIdx = 0;
int32_t iTotalFrameMax = -1;
uint8_t* pYUV = NULL;
SSourcePicture* pSrcPic = NULL;
uint32_t iSourceWidth, iSourceHeight, kiPicResSize;
// Inactive with sink with output file handler
FILE* pFpBs[4];
pFpBs[0] = pFpBs[1] = pFpBs[2] = pFpBs[3] = NULL;
#if defined(COMPARE_DATA)
//For getting the golden file handle
FILE* fpGolden = NULL;
#endif
#if defined ( STICK_STREAM_SIZE )
FILE* fTrackStream = fopen ("coding_size.stream", "wb");
#endif
SFilesSet fs;
// for configuration file
CReadConfig cRdCfg;
int iParsedNum = 1;
memset (&sFbi, 0, sizeof (SFrameBSInfo));
pPtrEnc->GetDefaultParams (&sSvcParam);
fs.bEnableMultiBsFile = false;
FillSpecificParameters (sSvcParam);
pSrcPic = new SSourcePicture;
if (pSrcPic == NULL) {
iRet = 1;
goto INSIDE_MEM_FREE;
}
//fill default pSrcPic
pSrcPic->iColorFormat = videoFormatI420;
pSrcPic->uiTimeStamp = 0;
// if configure file exit, reading configure file firstly
if (bConfigFile) {
iParsedNum = 2;
cRdCfg.Openf (argv[1]);
if (!cRdCfg.ExistFile()) {
fprintf (stderr, "Specified file: %s not exist, maybe invalid path or parameter settting.\n",
cRdCfg.GetFileName().c_str());
iRet = 1;
goto INSIDE_MEM_FREE;
}
iRet = ParseConfig (cRdCfg, pSrcPic, sSvcParam, fs);
if (iRet) {
fprintf (stderr, "parse svc parameter config file failed.\n");
iRet = 1;
goto INSIDE_MEM_FREE;
}
}
if (ParseCommandLine (argc - iParsedNum, argv + iParsedNum, pSrcPic, sSvcParam, fs) != 0) {
printf ("parse pCommand line failed\n");
iRet = 1;
goto INSIDE_MEM_FREE;
}
pPtrEnc->SetOption (ENCODER_OPTION_TRACE_LEVEL, &g_LevelSetting);
//finish reading the configurations
iSourceWidth = pSrcPic->iPicWidth;
iSourceHeight = pSrcPic->iPicHeight;
kiPicResSize = iSourceWidth * iSourceHeight * 3 >> 1;
pYUV = new uint8_t [kiPicResSize];
if (pYUV == NULL) {
iRet = 1;
goto INSIDE_MEM_FREE;
}
//update pSrcPic
pSrcPic->iStride[0] = iSourceWidth;
pSrcPic->iStride[1] = pSrcPic->iStride[2] = pSrcPic->iStride[0] >> 1;
pSrcPic->pData[0] = pYUV;
pSrcPic->pData[1] = pSrcPic->pData[0] + (iSourceWidth * iSourceHeight);
pSrcPic->pData[2] = pSrcPic->pData[1] + (iSourceWidth * iSourceHeight >> 2);
//update sSvcParam
sSvcParam.iPicWidth = 0;
sSvcParam.iPicHeight = 0;
for (int iLayer = 0; iLayer < sSvcParam.iSpatialLayerNum; iLayer++) {
SSpatialLayerConfig* pDLayer = &sSvcParam.sSpatialLayers[iLayer];
sSvcParam.iPicWidth = WELS_MAX (sSvcParam.iPicWidth, pDLayer->iVideoWidth);
sSvcParam.iPicHeight = WELS_MAX (sSvcParam.iPicHeight, pDLayer->iVideoHeight);
}
//if target output resolution is not set, use the source size
sSvcParam.iPicWidth = (!sSvcParam.iPicWidth) ? iSourceWidth : sSvcParam.iPicWidth;
sSvcParam.iPicHeight = (!sSvcParam.iPicHeight) ? iSourceHeight : sSvcParam.iPicHeight;
iTotalFrameMax = (int32_t)fs.uiFrameToBeCoded;
// sSvcParam.bSimulcastAVC = true;
if (cmResultSuccess != pPtrEnc->InitializeExt (&sSvcParam)) { // SVC encoder initialization
fprintf (stderr, "SVC encoder Initialize failed\n");
iRet = 1;
goto INSIDE_MEM_FREE;
}
for (int iLayer = 0; iLayer < MAX_DEPENDENCY_LAYER; iLayer++) {
if (fs.sRecFileName[iLayer][0] != 0) {
SDumpLayer sDumpLayer;
sDumpLayer.iLayer = iLayer;
sDumpLayer.pFileName = fs.sRecFileName[iLayer];
if (cmResultSuccess != pPtrEnc->SetOption (ENCODER_OPTION_DUMP_FILE, &sDumpLayer)) {
fprintf (stderr, "SetOption ENCODER_OPTION_DUMP_FILE failed!\n");
iRet = 1;
goto INSIDE_MEM_FREE;
}
}
}
// Inactive with sink with output file handler
if (fs.strBsFile.length() > 0) {
bool bFileOpenErr = false;
if (sSvcParam.iSpatialLayerNum == 1 || fs.bEnableMultiBsFile == false) {
pFpBs[0] = fopen (fs.strBsFile.c_str(), "wb");
bFileOpenErr = (pFpBs[0] == NULL);
} else { //enable multi bs file writing
string filename_layer;
string add_info[4] = {"_layer0", "_layer1", "_layer2", "_layer3"};
string::size_type found = fs.strBsFile.find_last_of ('.');
for (int i = 0; i < sSvcParam.iSpatialLayerNum; ++i) {
filename_layer = fs.strBsFile.insert (found, add_info[i]);
pFpBs[i] = fopen (filename_layer.c_str(), "wb");
fs.strBsFile = filename_layer.erase (found, 7);
bFileOpenErr |= (pFpBs[i] == NULL);
}
}
if (bFileOpenErr) {
fprintf (stderr, "Can not open file (%s) to write bitstream!\n", fs.strBsFile.c_str());
iRet = 1;
goto INSIDE_MEM_FREE;
}
} else {
fprintf (stderr, "Don't set the proper bitstream filename!\n");
iRet = 1;
goto INSIDE_MEM_FREE;
}
#if defined(COMPARE_DATA)
//For getting the golden file handle
if ((fpGolden = fopen (argv[3], "rb")) == NULL) {
fprintf (stderr, "Unable to open golden sequence file, check corresponding path!\n");
iRet = 1;
goto INSIDE_MEM_FREE;
}
#endif
pFileYUV = fopen (fs.strSeqFile.c_str(), "rb");
if (pFileYUV != NULL) {
#if defined(_WIN32) || defined(_WIN64)
#if _MSC_VER >= 1400
if (!_fseeki64 (pFileYUV, 0, SEEK_END)) {
int64_t i_size = _ftelli64 (pFileYUV);
_fseeki64 (pFileYUV, 0, SEEK_SET);
iTotalFrameMax = WELS_MAX ((int32_t) (i_size / kiPicResSize), iTotalFrameMax);
}
#else
if (!fseek (pFileYUV, 0, SEEK_END)) {
int64_t i_size = ftell (pFileYUV);
fseek (pFileYUV, 0, SEEK_SET);
iTotalFrameMax = WELS_MAX ((int32_t) (i_size / kiPicResSize), iTotalFrameMax);
}
#endif
#else
if (!fseeko (pFileYUV, 0, SEEK_END)) {
int64_t i_size = ftello (pFileYUV);
fseeko (pFileYUV, 0, SEEK_SET);
iTotalFrameMax = WELS_MAX ((int32_t) (i_size / kiPicResSize), iTotalFrameMax);
}
#endif
} else {
fprintf (stderr, "Unable to open source sequence file (%s), check corresponding path!\n",
fs.strSeqFile.c_str());
iRet = 1;
goto INSIDE_MEM_FREE;
}
iFrameIdx = 0;
while (iFrameIdx < iTotalFrameMax && (((int32_t)fs.uiFrameToBeCoded <= 0)
|| (iFrameIdx < (int32_t)fs.uiFrameToBeCoded))) {
#ifdef ONLY_ENC_FRAMES_NUM
// Only encoded some limited frames here
if (iActualFrameEncodedCount >= ONLY_ENC_FRAMES_NUM) {
break;
}
#endif//ONLY_ENC_FRAMES_NUM
bool bCanBeRead = false;
bCanBeRead = (fread (pYUV, 1, kiPicResSize, pFileYUV) == kiPicResSize);
if (!bCanBeRead)
break;
// To encoder this frame
iStart = WelsTime();
pSrcPic->uiTimeStamp = WELS_ROUND (iFrameIdx * (1000 / sSvcParam.fMaxFrameRate));
int iEncFrames = pPtrEnc->EncodeFrame (pSrcPic, &sFbi);
iTotal += WelsTime() - iStart;
++ iFrameIdx;
if (videoFrameTypeSkip == sFbi.eFrameType) {
continue;
}
if (iEncFrames == cmResultSuccess) {
int iLayer = 0;
int iFrameSize = 0;
while (iLayer < sFbi.iLayerNum) {
SLayerBSInfo* pLayerBsInfo = &sFbi.sLayerInfo[iLayer];
if (pLayerBsInfo != NULL) {
int iLayerSize = 0;
int iNalIdx = pLayerBsInfo->iNalCount - 1;
do {
iLayerSize += pLayerBsInfo->pNalLengthInByte[iNalIdx];
-- iNalIdx;
} while (iNalIdx >= 0);
#if defined(COMPARE_DATA)
//Comparing the result of encoder with golden pData
{
unsigned char* pUCArry = new unsigned char [iLayerSize];
fread (pUCArry, 1, iLayerSize, fpGolden);
for (int w = 0; w < iLayerSize; w++) {
if (pUCArry[w] != pLayerBsInfo->pBsBuf[w]) {
fprintf (stderr, "error @frame%d/layer%d/byte%d!!!!!!!!!!!!!!!!!!!!!!!!\n", iFrameIdx, iLayer, w);
//fprintf(stderr, "%x - %x\n", pUCArry[w], pLayerBsInfo->pBsBuf[w]);
break;
}
}
fprintf (stderr, "frame%d/layer%d comparation completed!\n", iFrameIdx, iLayer);
delete [] pUCArry;
}
#endif
if (sSvcParam.iSpatialLayerNum == 1 || fs.bEnableMultiBsFile == false)
fwrite (pLayerBsInfo->pBsBuf, 1, iLayerSize, pFpBs[0]); // write pure bit stream into file
else { //multi bs file write
if (pLayerBsInfo->uiSpatialId == 0) {
unsigned char five_bits = pLayerBsInfo->pBsBuf[4] & 0x1f;
if ((five_bits == 0x07) || (five_bits == 0x08)) { //sps or pps
for (int i = 0; i < sSvcParam.iSpatialLayerNum; ++i) {
fwrite (pLayerBsInfo->pBsBuf, 1, iLayerSize, pFpBs[i]);
}
} else {
fwrite (pLayerBsInfo->pBsBuf, 1, iLayerSize, pFpBs[0]);
}
} else {
fwrite (pLayerBsInfo->pBsBuf, 1, iLayerSize, pFpBs[pLayerBsInfo->uiSpatialId]);
}
}
iFrameSize += iLayerSize;
}
++ iLayer;
}
#if defined (STICK_STREAM_SIZE)
if (fTrackStream) {
fwrite (&iFrameSize, 1, sizeof (int), fTrackStream);
}
#endif//STICK_STREAM_SIZE
++ iActualFrameEncodedCount; // excluding skipped frame time
} else {
fprintf (stderr, "EncodeFrame(), ret: %d, frame index: %d.\n", iEncFrames, iFrameIdx);
}
}
if (iActualFrameEncodedCount > 0) {
double dElapsed = iTotal / 1e6;
printf ("Width:\t\t%d\nHeight:\t\t%d\nFrames:\t\t%d\nencode time:\t%f sec\nFPS:\t\t%f fps\n",
sSvcParam.iPicWidth, sSvcParam.iPicHeight,
iActualFrameEncodedCount, dElapsed, (iActualFrameEncodedCount * 1.0) / dElapsed);
#if defined (WINDOWS_PHONE)
g_fFPS = (iActualFrameEncodedCount * 1.0f) / (float) dElapsed;
g_dEncoderTime = dElapsed;
g_iEncodedFrame = iActualFrameEncodedCount;
#endif
}
INSIDE_MEM_FREE:
for (int i = 0; i < sSvcParam.iSpatialLayerNum; ++i) {
if (pFpBs[i]) {
fclose (pFpBs[i]);
pFpBs[i] = NULL;
}
}
#if defined (STICK_STREAM_SIZE)
if (fTrackStream) {
fclose (fTrackStream);
fTrackStream = NULL;
}
#endif
#if defined (COMPARE_DATA)
if (fpGolden) {
fclose (fpGolden);
fpGolden = NULL;
}
#endif
// Destruction memory introduced in this routine
if (pFileYUV != NULL) {
fclose (pFileYUV);
pFileYUV = NULL;
}
if (pYUV) {
delete[] pYUV;
pYUV = NULL;
}
if (pSrcPic) {
delete pSrcPic;
pSrcPic = NULL;
}
return iRet;
}
GetDefaultParams
函数
- api 函数,完成编码参数默认设置
/* Interfaces override from ISVCEncoder */
int CWelsH264SVCEncoder::GetDefaultParams (SEncParamExt* argv) {
SWelsSvcCodingParam::FillDefault (*argv);
return cmResultSuccess;
}
FillSpecificParameters
函数
- 填写特定参数
int FillSpecificParameters (SEncParamExt& sParam) {
/* Test for temporal, spatial, SNR scalability */
sParam.iUsageType = CAMERA_VIDEO_REAL_TIME;
sParam.fMaxFrameRate = 60.0f; // input frame rate
sParam.iPicWidth = 1280; // width of picture in samples
sParam.iPicHeight = 720; // height of picture in samples
sParam.iTargetBitrate = 2500000; // target bitrate desired
sParam.iMaxBitrate = UNSPECIFIED_BIT_RATE;
sParam.iRCMode = RC_QUALITY_MODE; // rc mode control
sParam.iTemporalLayerNum = 3; // layer number at temporal level
sParam.iSpatialLayerNum = 4; // layer number at spatial level
sParam.bEnableDenoise = 0; // denoise control
sParam.bEnableBackgroundDetection = 1; // background detection control
sParam.bEnableAdaptiveQuant = 1; // adaptive quantization control
sParam.bEnableFrameSkip = 1; // frame skipping
sParam.bEnableLongTermReference = 0; // long term reference control
sParam.iLtrMarkPeriod = 30;
sParam.uiIntraPeriod = 320; // period of Intra frame
sParam.eSpsPpsIdStrategy = INCREASING_ID;
sParam.bPrefixNalAddingCtrl = 0;
sParam.iComplexityMode = LOW_COMPLEXITY;
sParam.bSimulcastAVC = false;
int iIndexLayer = 0;
sParam.sSpatialLayers[iIndexLayer].uiProfileIdc = PRO_BASELINE;
sParam.sSpatialLayers[iIndexLayer].iVideoWidth = 160;
sParam.sSpatialLayers[iIndexLayer].iVideoHeight = 90;
sParam.sSpatialLayers[iIndexLayer].fFrameRate = 7.5f;
sParam.sSpatialLayers[iIndexLayer].iSpatialBitrate = 64000;
sParam.sSpatialLayers[iIndexLayer].iMaxSpatialBitrate = UNSPECIFIED_BIT_RATE;
sParam.sSpatialLayers[iIndexLayer].sSliceArgument.uiSliceMode = SM_SINGLE_SLICE;
++ iIndexLayer;
sParam.sSpatialLayers[iIndexLayer].uiProfileIdc = PRO_SCALABLE_BASELINE;
sParam.sSpatialLayers[iIndexLayer].iVideoWidth = 320;
sParam.sSpatialLayers[iIndexLayer].iVideoHeight = 180;
sParam.sSpatialLayers[iIndexLayer].fFrameRate = 15.0f;
sParam.sSpatialLayers[iIndexLayer].iSpatialBitrate = 160000;
sParam.sSpatialLayers[iIndexLayer].iMaxSpatialBitrate = UNSPECIFIED_BIT_RATE;
sParam.sSpatialLayers[iIndexLayer].sSliceArgument.uiSliceMode = SM_SINGLE_SLICE;
++ iIndexLayer;
sParam.sSpatialLayers[iIndexLayer].uiProfileIdc = PRO_SCALABLE_BASELINE;
sParam.sSpatialLayers[iIndexLayer].iVideoWidth = 640;
sParam.sSpatialLayers[iIndexLayer].iVideoHeight = 360;
sParam.sSpatialLayers[iIndexLayer].fFrameRate = 30.0f;
sParam.sSpatialLayers[iIndexLayer].iSpatialBitrate = 512000;
sParam.sSpatialLayers[iIndexLayer].iMaxSpatialBitrate = UNSPECIFIED_BIT_RATE;
sParam.sSpatialLayers[iIndexLayer].sSliceArgument.uiSliceMode = SM_SINGLE_SLICE;
sParam.sSpatialLayers[iIndexLayer].sSliceArgument.uiSliceNum = 1;
++ iIndexLayer;
sParam.sSpatialLayers[iIndexLayer].uiProfileIdc = PRO_SCALABLE_BASELINE;
sParam.sSpatialLayers[iIndexLayer].iVideoWidth = 1280;
sParam.sSpatialLayers[iIndexLayer].iVideoHeight = 720;
sParam.sSpatialLayers[iIndexLayer].fFrameRate = 30.0f;
sParam.sSpatialLayers[iIndexLayer].iSpatialBitrate = 1500000;
sParam.sSpatialLayers[iIndexLayer].iMaxSpatialBitrate = UNSPECIFIED_BIT_RATE;
sParam.sSpatialLayers[iIndexLayer].sSliceArgument.uiSliceMode = SM_SINGLE_SLICE;
sParam.sSpatialLayers[iIndexLayer].sSliceArgument.uiSliceNum = 1;
float fMaxFr = sParam.sSpatialLayers[sParam.iSpatialLayerNum - 1].fFrameRate;
for (int32_t i = sParam.iSpatialLayerNum - 2; i >= 0; -- i) {
if (sParam.sSpatialLayers[i].fFrameRate > fMaxFr + EPSN)
fMaxFr = sParam.sSpatialLayers[i].fFrameRate;
}
sParam.fMaxFrameRate = fMaxFr;
return 0;
}
ParseConfig
函数
- 解析 config 文件的函数
int ParseConfig (CReadConfig& cRdCfg, SSourcePicture* pSrcPic, SEncParamExt& pSvcParam, SFilesSet& sFileSet) {
//代码有删减
return iRet;
}
ParseCommandLine
函数
解析命令行函数
int ParseCommandLine (int argc, char** argv, SSourcePicture* pSrcPic, SEncParamExt& pSvcParam, SFilesSet& sFileSet) {
//代码有删减
return 0;
}
SetOption
函数
- api 函数,设置编码选项
/************************************************************************
* InDataFormat, IDRInterval, SVC Encode Param, Frame Rate, Bitrate,..
************************************************************************/
int CWelsH264SVCEncoder::SetOption (ENCODER_OPTION eOptionId, void* pOption) {
//代码有删减
return 0;
}
InitializeExt
函数
- api 函数,初始化编码器
int CWelsH264SVCEncoder::InitializeExt (const SEncParamExt* argv) {
if (m_pWelsTrace == NULL) {
return cmMallocMemeError;
}
WelsLog (&m_pWelsTrace->m_sLogCtx, WELS_LOG_INFO, "CWelsH264SVCEncoder::InitEncoder(), openh264 codec version = %s",
VERSION_NUMBER);
if (NULL == argv) {
WelsLog (&m_pWelsTrace->m_sLogCtx, WELS_LOG_ERROR, "CWelsH264SVCEncoder::InitializeExt(), invalid argv= 0x%p",
argv);
return cmInitParaError;
}
SWelsSvcCodingParam sConfig;
// Convert SEncParamExt into WelsSVCParamConfig here..
if (sConfig.ParamTranscode (*argv)) {
WelsLog (&m_pWelsTrace->m_sLogCtx, WELS_LOG_ERROR,
"CWelsH264SVCEncoder::InitializeExt(), parameter_translation failed.");
TraceParamInfo (&sConfig);
Uninitialize();
return cmInitParaError;
}
return InitializeInternal (&sConfig);
}
EncodeFrame
函数
- api 函数,完成具体的编码功能,是 openh264 编码模块的核心模块。
*
* SVC core encoding
*/
int CWelsH264SVCEncoder::EncodeFrame (const SSourcePicture* kpSrcPic, SFrameBSInfo* pBsInfo) {
if (! (kpSrcPic && m_bInitialFlag && pBsInfo)) {
WelsLog (&m_pWelsTrace->m_sLogCtx, WELS_LOG_ERROR, "CWelsH264SVCEncoder::EncodeFrame(), cmInitParaError.");
return cmInitParaError;
}
if (kpSrcPic->iColorFormat != videoFormatI420) {
WelsLog (&m_pWelsTrace->m_sLogCtx, WELS_LOG_ERROR, "CWelsH264SVCEncoder::EncodeFrame(), wrong iColorFormat %d",
kpSrcPic->iColorFormat);
return cmInitParaError;
}
const int32_t kiEncoderReturn = EncodeFrameInternal (kpSrcPic, pBsInfo);
if (kiEncoderReturn != cmResultSuccess) {
WelsLog (&m_pWelsTrace->m_sLogCtx, WELS_LOG_ERROR, "CWelsH264SVCEncoder::EncodeFrame(), kiEncoderReturn %d",
kiEncoderReturn);
return kiEncoderReturn;
}
#ifdef REC_FRAME_COUNT
++ m_uiCountFrameNum;
WelsLog (&m_pWelsTrace->m_sLogCtx, WELS_LOG_INFO,
"CWelsH264SVCEncoder::EncodeFrame(), m_uiCountFrameNum= %d,", m_uiCountFrameNum);
#endif//REC_FRAME_COUNT
return kiEncoderReturn;
}
销毁编码器
DestroySVCEncHandle
函数
- 该函数销毁编码实例ISVCEncoder,函数定义如下,可以看到,内部就是调用 api
函数WelsCreateSVCEncoder完成编码实例销毁。
void DestroySVCEncHandle (ISVCEncoder* pEncoder) {
if (pEncoder) {
WelsDestroySVCEncoder (pEncoder);
}
}
WelsCreateSVCEncoder
函数
- 该函数作为 api 函数,在内部完成 delete编码器类CWelsH264SVCEncoder的功能,函数定义如下,因此可以看到CWelsH264SVCEncoder类是进行编码类,继承于ISVCEncoder类。
void WelsDestroySVCEncoder (ISVCEncoder* pEncoder) {
CWelsH264SVCEncoder* pSVCEncoder = (CWelsH264SVCEncoder*)pEncoder;
if (pSVCEncoder) {
delete pSVCEncoder;
pSVCEncoder = NULL;
}
}
后续
- 后续针对编码核心模块
EncodeFrame
函数进行分析。