diff --git a/codec/api/svc/codec_app_def.h b/codec/api/svc/codec_app_def.h index 6b2802a2..6c4e456b 100644 --- a/codec/api/svc/codec_app_def.h +++ b/codec/api/svc/codec_app_def.h @@ -103,6 +103,9 @@ typedef enum { ENCODER_OPTION_TRACE_CALLBACK, // a void (*)(void* context, int level, const char* message) function which receives log messages ENCODER_OPTION_TRACE_CALLBACK_CONTEXT, + ENCODER_OPTION_GET_STATISTICS, //read only + ENCODER_OPTION_STATISTICS_LOG_INTERVAL, // log interval in milliseconds + // advanced algorithmetic settings ENCODER_OPTION_IS_LOSSLESS_LINK } ENCODER_OPTION; @@ -120,7 +123,9 @@ typedef enum { DECODER_OPTION_ERROR_CON_IDC, //not finished yet, indicate decoder error concealment status, in progress DECODER_OPTION_TRACE_LEVEL, DECODER_OPTION_TRACE_CALLBACK, // a void (*)(void* context, int level, const char* message) function which receives log messages - DECODER_OPTION_TRACE_CALLBACK_CONTEXT + DECODER_OPTION_TRACE_CALLBACK_CONTEXT, + + DECODER_OPTION_GET_STATISTICS } DECODER_OPTION; @@ -455,4 +460,38 @@ typedef struct TagParserBsInfo { int iSpsHeightInPixel; //required SPS height info } SParserBsInfo, PParserBsInfo; +typedef struct TagVideoEncoderStatistics { + unsigned int uWidth; // the width of encoded frame + unsigned int uHeight; // the height of encoded frame + //following standard, will be 16x aligned, if there are multiple spatial, this is of the highest + float fAverageFrameSpeedInMs; // Average_Encoding_Time + + // rate control related + float fAverageFrameRate; // the average frame rate in, calculate since encoding starts, supposed that the input timestamp is in unit of ms + float fLatestFrameRate; // the frame rate in, in the last second, supposed that the input timestamp is in unit of ms (? useful for checking BR, but is it easy to calculate? + unsigned int uiBitRate; // sendrate in Bits per second, calculated within the set time-window + + unsigned int uiInputFrameCount; // number of frames + unsigned int uiSkippedFrameCount; // number of frames + + unsigned int uiResolutionChangeTimes; // uiResolutionChangeTimes + unsigned int uIDRReqNum; // number of IDR requests + unsigned int uIDRSentNum; // number of actual IDRs sent + unsigned int uLTRSentNum; // number of LTR sent/marked +} SEncoderStatistics; // in building, coming soon + +typedef struct TagVideoDecoderStatistics { + unsigned int uWidth; // the width of encode/decode frame + unsigned int uHeight; // the height of encode/decode frame + float fAverageFrameSpeedInMs; // Average_Decoding_Time + + unsigned int uiDecodedFrameCount; // number of frames + unsigned int uiResolutionChangeTimes; // uiResolutionChangeTimes + unsigned int + uiAvgEcRatio; // when EC is on, the average ratio of correct or EC areas, can be an indicator of reconstruction quality + unsigned int uIDRReqNum; // number of actual IDR request + unsigned int uLTRReqNum; // number of actual LTR request + unsigned int uIDRRecvNum; // number of actual IDR received +} SDecoderStatistics; // in building, coming soon + #endif//WELS_VIDEO_CODEC_APPLICATION_DEFINITION_H__ diff --git a/codec/encoder/core/inc/encoder_context.h b/codec/encoder/core/inc/encoder_context.h index 32086c3f..e21a26e6 100644 --- a/codec/encoder/core/inc/encoder_context.h +++ b/codec/encoder/core/inc/encoder_context.h @@ -216,6 +216,12 @@ typedef struct TagWelsEncCtx { SStatSliceInfo sPerInfo; #endif//STAT_OUTPUT + //related to Statistics + int64_t uiStartTimestamp; + SEncoderStatistics sEncoderStatistics; + int32_t iStatisticsLogInterval; + int64_t iLastStatisticsLogTs; + int32_t iEncoderError; WELS_MUTEX mutexEncoderError; bool bDeliveryFlag; diff --git a/codec/encoder/core/inc/rc.h b/codec/encoder/core/inc/rc.h index 93f0c35f..2dcd6b8d 100644 --- a/codec/encoder/core/inc/rc.h +++ b/codec/encoder/core/inc/rc.h @@ -224,6 +224,10 @@ int64_t iAvgCost2Bits; int64_t iCost2BitsIntra; int32_t iBaseQp; long long uiLastTimeStamp; + +//for statistics and online adjustments +int32_t iActualBitRate; // TODO: to complete later +float fLatestFrameRate; // TODO: to complete later } SWelsSvcRc; typedef void (*PWelsRCPictureInitFunc) (void* pCtx); diff --git a/codec/encoder/core/inc/wels_const.h b/codec/encoder/core/inc/wels_const.h index cf4fd9b4..e481b9ed 100644 --- a/codec/encoder/core/inc/wels_const.h +++ b/codec/encoder/core/inc/wels_const.h @@ -55,6 +55,8 @@ #define MAX_TRACE_LOG_SIZE (50 * (1<<20)) // max trace log size: 50 MB, overwrite occur if log file size exceeds this size #endif//MAX_TRACE_LOG_SIZE +#define STATISTICS_LOG_INTERVAL_MS (5000) // output statistics log every 5s + /* MB width in pixels for specified colorspace I420 usually used in codec */ #define MB_WIDTH_LUMA 16 #define MB_WIDTH_CHROMA (MB_WIDTH_LUMA>>1) diff --git a/codec/encoder/core/src/encoder_ext.cpp b/codec/encoder/core/src/encoder_ext.cpp index 2cd7bbb7..b5917b5c 100644 --- a/codec/encoder/core/src/encoder_ext.cpp +++ b/codec/encoder/core/src/encoder_ext.cpp @@ -2085,6 +2085,8 @@ int32_t WelsInitEncoderExt (sWelsEncCtx** ppCtx, SWelsSvcCodingParam* pCodingPar ); #endif//MEMORY_MONITOR + pCtx->iStatisticsLogInterval = STATISTICS_LOG_INTERVAL_MS; + *ppCtx = pCtx; WelsLog (pLogCtx, WELS_LOG_DEBUG, "WelsInitEncoderExt(), pCtx= 0x%p.", (void*)pCtx); @@ -3736,6 +3738,8 @@ int32_t WelsEncoderParamAdjust (sWelsEncCtx** ppCtx, SWelsSvcCodingParam* pNewPa (PARA_SET_TYPE)*sizeof (SParaSetOffsetVariable)); // confirmed_safe_unsafe_usage uiTmpIdrPicId = (*ppCtx)->sPSOVector.uiIdrPicId; + SEncoderStatistics sTempEncoderStatistics = (*ppCtx)->sEncoderStatistics; + WelsUninitEncoderExt (ppCtx); /* Update new parameters */ @@ -3746,10 +3750,13 @@ int32_t WelsEncoderParamAdjust (sWelsEncCtx** ppCtx, SWelsSvcCodingParam* pNewPa (*ppCtx)->pVpp->WelsPreprocessReset (*ppCtx); //if WelsInitEncoderExt succeed + //load back the needed structure //for FLEXIBLE_PARASET_ID memcpy ((*ppCtx)->sPSOVector.sParaSetOffsetVariable, sTmpPsoVariable, (PARA_SET_TYPE)*sizeof (SParaSetOffsetVariable)); // confirmed_safe_unsafe_usage (*ppCtx)->sPSOVector.uiIdrPicId = uiTmpIdrPicId; + //for sEncoderStatistics + (*ppCtx)->sEncoderStatistics = sTempEncoderStatistics; } else { /* maybe adjustment introduced in bitrate or little settings adjustment and so on.. */ pNewParam->iNumRefFrame = WELS_CLIP3 (pNewParam->iNumRefFrame, MIN_REF_PIC_COUNT, diff --git a/codec/encoder/plus/inc/welsEncoderExt.h b/codec/encoder/plus/inc/welsEncoderExt.h index ae93545e..d9992f08 100644 --- a/codec/encoder/plus/inc/welsEncoderExt.h +++ b/codec/encoder/plus/inc/welsEncoderExt.h @@ -102,6 +102,8 @@ class CWelsH264SVCEncoder : public ISVCEncoder { void CheckLevelSetting (int32_t iLayer, ELevelIdc uiLevelIdc); void CheckReferenceNumSetting (int32_t iNumRef); void TraceParamInfo(SEncParamExt *pParam); + void UpdateStatistics(const int64_t kiCurrentFrameTs, EVideoFrameType eFrameType, const int64_t kiCurrentFrameMs); + sWelsEncCtx* m_pEncContext; welsCodecTrace* m_pWelsTrace; diff --git a/codec/encoder/plus/src/welsEncoderExt.cpp b/codec/encoder/plus/src/welsEncoderExt.cpp index 8513a4bb..6393e4e6 100644 --- a/codec/encoder/plus/src/welsEncoderExt.cpp +++ b/codec/encoder/plus/src/welsEncoderExt.cpp @@ -42,6 +42,7 @@ #include "ref_list_mgr_svc.h" #include +#include #if defined(_WIN32) /*&& defined(_DEBUG)*/ #include @@ -416,8 +417,10 @@ int CWelsH264SVCEncoder::EncodeFrame (const SSourcePicture* kpSrcPic, SFrameBSIn } -int CWelsH264SVCEncoder::EncodeFrameInternal (const SSourcePicture* pSrcPic, SFrameBSInfo* pBsInfo) { +int CWelsH264SVCEncoder ::EncodeFrameInternal (const SSourcePicture* pSrcPic, SFrameBSInfo* pBsInfo) { + const int64_t kiBeforeFrameUs = WelsTime(); const int32_t kiEncoderReturn = WelsEncoderEncodeExt (m_pEncContext, pBsInfo, pSrcPic); + const int64_t kiCurrentFrameMs = (WelsTime() - kiBeforeFrameUs) / 1000; if (kiEncoderReturn == ENC_RETURN_MEMALLOCERR) { WelsUninitEncoderExt (&m_pEncContext); @@ -427,6 +430,9 @@ int CWelsH264SVCEncoder::EncodeFrameInternal (const SSourcePicture* pSrcPic, SF kiEncoderReturn); return cmUnkonwReason; } + + UpdateStatistics (pSrcPic->uiTimeStamp, pBsInfo->eFrameType, kiCurrentFrameMs); + ///////////////////for test #ifdef OUTPUT_BIT_STREAM if (pBsInfo->eFrameType != videoFrameTypeInvalid && pBsInfo->eFrameType != videoFrameTypeSkip) { @@ -586,6 +592,61 @@ void CWelsH264SVCEncoder::TraceParamInfo (SEncParamExt* pParam) { ++ i; } } + +void CWelsH264SVCEncoder::UpdateStatistics (const int64_t kiCurrentFrameTs, EVideoFrameType eFrameType, + const int64_t kiCurrentFrameMs) { + SEncoderStatistics* pStatistics = & (m_pEncContext->sEncoderStatistics); + + int32_t iMaxDid = m_pEncContext->pSvcParam->iSpatialLayerNum - 1; + if ((0 != pStatistics->uWidth && 0 != pStatistics->uHeight) + && (pStatistics->uWidth != m_pEncContext->pSvcParam->sDependencyLayers[iMaxDid].iActualWidth + || pStatistics->uHeight != m_pEncContext->pSvcParam->sDependencyLayers[iMaxDid].iActualHeight)) { + pStatistics->uiResolutionChangeTimes ++; + } + pStatistics->uWidth = m_pEncContext->pSvcParam->sDependencyLayers[iMaxDid].iActualWidth; + pStatistics->uHeight = m_pEncContext->pSvcParam->sDependencyLayers[iMaxDid].iActualHeight; + + int32_t iProcessedFrameCount = pStatistics->uiInputFrameCount - pStatistics->uiSkippedFrameCount; + const bool kbCurrentFrameSkipped = (videoFrameTypeSkip == eFrameType); + if (!kbCurrentFrameSkipped && (iProcessedFrameCount + 1) != 0) { + pStatistics->fAverageFrameSpeedInMs = (iProcessedFrameCount * pStatistics->fAverageFrameSpeedInMs + + kiCurrentFrameMs) / (iProcessedFrameCount + 1); + } + pStatistics->uiInputFrameCount ++; + pStatistics->uiSkippedFrameCount += (kbCurrentFrameSkipped ? 1 : 0); + + // rate control related + if (0 != m_pEncContext->uiStartTimestamp && kiCurrentFrameTs > m_pEncContext->uiStartTimestamp + 800) { + pStatistics->fAverageFrameRate = pStatistics->uiInputFrameCount * 1000 / + (kiCurrentFrameTs - m_pEncContext->uiStartTimestamp); + } else { + m_pEncContext->uiStartTimestamp = kiCurrentFrameTs; + } + pStatistics->fLatestFrameRate = m_pEncContext->pWelsSvcRc->fLatestFrameRate; //TODO: finish the calculation in RC + pStatistics->uiBitRate = m_pEncContext->pWelsSvcRc->iActualBitRate; //TODO: finish the calculation in RC + + if (videoFrameTypeIDR == eFrameType || videoFrameTypeI == eFrameType) { + pStatistics->uIDRSentNum ++; + } + if (m_pEncContext->pLtr->bLTRMarkingFlag) { + pStatistics->uLTRSentNum ++; + } + //TODO: update uIDRSentNum in forceIDR + + if (m_pEncContext->iStatisticsLogInterval > 0) { + if (WELS_ABS (kiCurrentFrameTs - m_pEncContext->iLastStatisticsLogTs) > m_pEncContext->iStatisticsLogInterval) { + WelsLog (&m_pWelsTrace->m_sLogCtx, WELS_LOG_INFO, + "EncoderStatistics: %dx%d, SpeedInMs: %f, AverFrameRate=%f, LastFrameRate=%f, LatestBitRate=%d, uiInputFrameCount=%d, uiSkippedFrameCount=%d, uiResolutionChangeTimes=%d, uIDRReqNum=%d, uIDRSentNum=%d, uLTRSentNum=%d", + pStatistics->uWidth, pStatistics->uHeight, pStatistics->fAverageFrameSpeedInMs, + pStatistics->fAverageFrameRate, pStatistics->fLatestFrameRate, pStatistics->uiBitRate, + pStatistics->uiInputFrameCount, pStatistics->uiSkippedFrameCount, + pStatistics->uiResolutionChangeTimes, pStatistics->uIDRReqNum, pStatistics->uIDRSentNum, pStatistics->uLTRSentNum); + m_pEncContext->iLastStatisticsLogTs = kiCurrentFrameTs; + } + } + +} + /************************************************************************ * InDataFormat, IDRInterval, SVC Encode Param, Frame Rate, Bitrate,.. ************************************************************************/ @@ -919,6 +980,16 @@ int CWelsH264SVCEncoder::SetOption (ENCODER_OPTION eOptionId, void* pOption) { m_pEncContext->pSvcParam->iComplexityMode = (ECOMPLEXITY_MODE)iValue; } break; + case ENCODER_OPTION_GET_STATISTICS: { + WelsLog (&m_pWelsTrace->m_sLogCtx, WELS_LOG_WARNING, + "CWelsH264SVCEncoder::SetOption():ENCODER_OPTION_GET_STATISTICS: this option is get-only!"); + } + break; + case ENCODER_OPTION_STATISTICS_LOG_INTERVAL: { + int32_t iValue = * (static_cast (pOption)); + m_pEncContext->iStatisticsLogInterval = iValue; + } + break; case ENCODER_OPTION_IS_LOSSLESS_LINK: { bool bValue = * (static_cast (pOption)); m_pEncContext->pSvcParam->bIsLosslessLink = bValue; @@ -1026,6 +1097,30 @@ int CWelsH264SVCEncoder::GetOption (ENCODER_OPTION eOptionId, void* pOption) { } } break; + case ENCODER_OPTION_GET_STATISTICS: { + SEncoderStatistics* pStatistics = (static_cast (pOption)); + pStatistics->uWidth = m_pEncContext->sEncoderStatistics.uWidth; + pStatistics->uHeight = m_pEncContext->sEncoderStatistics.uHeight; + pStatistics->fAverageFrameSpeedInMs = m_pEncContext->sEncoderStatistics.fAverageFrameSpeedInMs; + + // rate control related + pStatistics->fAverageFrameRate = m_pEncContext->sEncoderStatistics.fAverageFrameRate; + pStatistics->fLatestFrameRate = m_pEncContext->sEncoderStatistics.fLatestFrameRate; + pStatistics->uiBitRate = m_pEncContext->sEncoderStatistics.uiBitRate; + + pStatistics->uiInputFrameCount = m_pEncContext->sEncoderStatistics.uiInputFrameCount; + pStatistics->uiSkippedFrameCount = m_pEncContext->sEncoderStatistics.uiSkippedFrameCount; + + pStatistics->uiResolutionChangeTimes = m_pEncContext->sEncoderStatistics.uiResolutionChangeTimes; + pStatistics->uIDRReqNum = m_pEncContext->sEncoderStatistics.uIDRReqNum; + pStatistics->uIDRSentNum = m_pEncContext->sEncoderStatistics.uIDRSentNum; + pStatistics->uLTRSentNum = m_pEncContext->sEncoderStatistics.uLTRSentNum; + } + break; + case ENCODER_OPTION_STATISTICS_LOG_INTERVAL: { + * ((int32_t*)pOption) = m_pEncContext->iStatisticsLogInterval; + } + break; case ENCODER_OPTION_COMPLEXITY: { * ((int32_t*)pOption) = m_pEncContext->pSvcParam->iComplexityMode; } diff --git a/test/encoder/EncUT_EncoderExt.cpp b/test/encoder/EncUT_EncoderExt.cpp index 6a98b81b..fce928e5 100644 --- a/test/encoder/EncUT_EncoderExt.cpp +++ b/test/encoder/EncUT_EncoderExt.cpp @@ -661,3 +661,83 @@ TEST_F (EncoderInterfaceTest, EncodeParameterSets) { TEST_F (EncoderInterfaceTest, BasicReturnTypeTest) { //TODO } + +TEST_F (EncoderInterfaceTest, GetStatistics) { + SEncParamBase sEncParamBase; + GetValidEncParamBase (&sEncParamBase); + + int iResult = pPtrEnc->Initialize (&sEncParamBase); + EXPECT_EQ (iResult, static_cast (cmResultSuccess)); + if (iResult != cmResultSuccess) { + fprintf (stderr, "Unexpected ParamBase? \ + iUsageType=%d, Pic=%dx%d, TargetBitrate=%d, iRCMode=%d, fMaxFrameRate=%.1f\n", + sEncParamBase.iUsageType, sEncParamBase.iPicWidth, sEncParamBase.iPicHeight, + sEncParamBase.iTargetBitrate, sEncParamBase.iRCMode, sEncParamBase.fMaxFrameRate); + } + + PrepareOneSrcFrame(); + EncodeOneIDRandP (pPtrEnc); + + SEncoderStatistics sEncoderStatistics; + iResult = pPtrEnc->GetOption (ENCODER_OPTION_GET_STATISTICS, &sEncoderStatistics); + EXPECT_EQ (iResult, static_cast (cmResultSuccess)); + EXPECT_EQ (sEncoderStatistics.uiInputFrameCount, 2); + EXPECT_EQ (sEncoderStatistics.uIDRSentNum, 1); + EXPECT_EQ (sEncoderStatistics.uiResolutionChangeTimes, 0); + + EXPECT_EQ (sEncoderStatistics.uWidth, sEncParamBase.iPicWidth); + EXPECT_EQ (sEncoderStatistics.uHeight, sEncParamBase.iPicHeight); + + // try param change + // 1, get the existing + pPtrEnc->GetOption (ENCODER_OPTION_SVC_ENCODE_PARAM_EXT, pParamExt); + EXPECT_EQ (iResult, static_cast (cmResultSuccess)); + + // 2, change the reoslution + GetValidEncParamBase (&sEncParamBase); + int32_t knownResolutionChangeTimes = 0; + if (pParamExt->iPicWidth != sEncParamBase.iPicWidth || pParamExt->iPicHeight != sEncParamBase.iPicHeight) { + knownResolutionChangeTimes = 1; + } + pParamExt->iPicWidth = pParamExt->sSpatialLayers[0].iVideoWidth = sEncParamBase.iPicWidth; + pParamExt->iPicHeight = pParamExt->sSpatialLayers[0].iVideoHeight = sEncParamBase.iPicHeight; + pPtrEnc->SetOption (ENCODER_OPTION_SVC_ENCODE_PARAM_EXT, pParamExt); + EXPECT_EQ (iResult, static_cast (cmResultSuccess)); + + // 3, code one frame + PrepareOneSrcFrame(); + iResult = pPtrEnc->EncodeFrame (pSrcPic, &sFbi); + EXPECT_EQ (iResult, static_cast (cmResultSuccess)); + iResult = pPtrEnc->GetOption (ENCODER_OPTION_GET_STATISTICS, &sEncoderStatistics); + EXPECT_EQ (iResult, static_cast (cmResultSuccess)); + + EXPECT_EQ (sEncoderStatistics.uiInputFrameCount, 3); + EXPECT_EQ (sEncoderStatistics.uIDRSentNum, 2); + EXPECT_EQ (sEncoderStatistics.uiResolutionChangeTimes, knownResolutionChangeTimes); + + EXPECT_EQ (sEncoderStatistics.uWidth, sEncParamBase.iPicWidth); + EXPECT_EQ (sEncoderStatistics.uHeight, sEncParamBase.iPicHeight); + + // 4, change log interval + int32_t iInterval = 0; + iResult = pPtrEnc->GetOption (ENCODER_OPTION_STATISTICS_LOG_INTERVAL, &iInterval); + EXPECT_EQ (iResult, static_cast (cmResultSuccess)); + EXPECT_EQ (iInterval, 5000); + + int32_t iInterval2 = 2000; + iResult = pPtrEnc->SetOption (ENCODER_OPTION_STATISTICS_LOG_INTERVAL, &iInterval2); + EXPECT_EQ (iResult, static_cast (cmResultSuccess)); + iResult = pPtrEnc->GetOption (ENCODER_OPTION_STATISTICS_LOG_INTERVAL, &iInterval); + EXPECT_EQ (iResult, static_cast (cmResultSuccess)); + EXPECT_EQ (iInterval, iInterval2); + + iInterval2 = 0; + iResult = pPtrEnc->SetOption (ENCODER_OPTION_STATISTICS_LOG_INTERVAL, &iInterval2); + EXPECT_EQ (iResult, static_cast (cmResultSuccess)); + iResult = pPtrEnc->GetOption (ENCODER_OPTION_STATISTICS_LOG_INTERVAL, &iInterval); + EXPECT_EQ (iResult, static_cast (cmResultSuccess)); + EXPECT_EQ (iInterval, iInterval2); + + // finish + pPtrEnc->Uninitialize(); +} \ No newline at end of file