shithub: openh264

Download patch

ref: a765197b73a7b58c1d2a2bbce135fd1b9bf9deab
parent: d720122a3765ad881575c73def3492b2022c8c01
author: Sijia Chen <sijchen@cisco.com>
date: Wed Oct 22 07:35:17 EDT 2014

add interface and basic implementaion and UT for EncoderStatistics

--- a/codec/api/svc/codec_app_def.h
+++ b/codec/api/svc/codec_app_def.h
@@ -103,6 +103,9 @@
   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,8 +123,10 @@
   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;
 
 //enuerate the types of error concealment methods
@@ -454,5 +459,39 @@
   int iSpsWidthInPixel; //required SPS width info
   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__
--- a/codec/encoder/core/inc/encoder_context.h
+++ b/codec/encoder/core/inc/encoder_context.h
@@ -216,6 +216,12 @@
   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;
--- a/codec/encoder/core/inc/rc.h
+++ b/codec/encoder/core/inc/rc.h
@@ -224,6 +224,10 @@
 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);
--- 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)
--- a/codec/encoder/core/src/encoder_ext.cpp
+++ b/codec/encoder/core/src/encoder_ext.cpp
@@ -2085,6 +2085,8 @@
           );
 #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 @@
             (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 @@
     (*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,
--- a/codec/encoder/plus/inc/welsEncoderExt.h
+++ b/codec/encoder/plus/inc/welsEncoderExt.h
@@ -102,6 +102,8 @@
   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;
--- 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 <time.h>
+#include <measure_time.h>
 #if defined(_WIN32) /*&& defined(_DEBUG)*/
 
 #include <windows.h>
@@ -416,8 +417,10 @@
 }
 
 
-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 @@
              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 @@
     ++ 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 @@
     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<int32_t*> (pOption));
+    m_pEncContext->iStatisticsLogInterval = iValue;
+  }
+  break;
   case ENCODER_OPTION_IS_LOSSLESS_LINK: {
     bool bValue = * (static_cast<bool*> (pOption));
     m_pEncContext->pSvcParam->bIsLosslessLink = bValue;
@@ -1024,6 +1095,30 @@
     } else {
       pInfo->iBitrate = m_pEncContext->pSvcParam->sSpatialLayers[pInfo->iLayer].iMaxSpatialBitrate;
     }
+  }
+  break;
+  case ENCODER_OPTION_GET_STATISTICS: {
+    SEncoderStatistics* pStatistics = (static_cast<SEncoderStatistics*> (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: {
--- a/test/encoder/EncUT_EncoderExt.cpp
+++ b/test/encoder/EncUT_EncoderExt.cpp
@@ -661,3 +661,83 @@
 TEST_F (EncoderInterfaceTest, BasicReturnTypeTest) {
   //TODO
 }
+
+TEST_F (EncoderInterfaceTest, GetStatistics) {
+  SEncParamBase sEncParamBase;
+  GetValidEncParamBase (&sEncParamBase);
+
+  int iResult = pPtrEnc->Initialize (&sEncParamBase);
+  EXPECT_EQ (iResult, static_cast<int> (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<int> (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<int> (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<int> (cmResultSuccess));
+
+  // 3, code one frame
+  PrepareOneSrcFrame();
+  iResult = pPtrEnc->EncodeFrame (pSrcPic, &sFbi);
+  EXPECT_EQ (iResult, static_cast<int> (cmResultSuccess));
+  iResult = pPtrEnc->GetOption (ENCODER_OPTION_GET_STATISTICS, &sEncoderStatistics);
+  EXPECT_EQ (iResult, static_cast<int> (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<int> (cmResultSuccess));
+  EXPECT_EQ (iInterval, 5000);
+
+  int32_t iInterval2 = 2000;
+  iResult = pPtrEnc->SetOption (ENCODER_OPTION_STATISTICS_LOG_INTERVAL, &iInterval2);
+  EXPECT_EQ (iResult, static_cast<int> (cmResultSuccess));
+  iResult = pPtrEnc->GetOption (ENCODER_OPTION_STATISTICS_LOG_INTERVAL, &iInterval);
+  EXPECT_EQ (iResult, static_cast<int> (cmResultSuccess));
+  EXPECT_EQ (iInterval, iInterval2);
+
+  iInterval2 = 0;
+  iResult = pPtrEnc->SetOption (ENCODER_OPTION_STATISTICS_LOG_INTERVAL, &iInterval2);
+  EXPECT_EQ (iResult, static_cast<int> (cmResultSuccess));
+  iResult = pPtrEnc->GetOption (ENCODER_OPTION_STATISTICS_LOG_INTERVAL, &iInterval);
+  EXPECT_EQ (iResult, static_cast<int> (cmResultSuccess));
+  EXPECT_EQ (iInterval, iInterval2);
+
+  // finish
+  pPtrEnc->Uninitialize();
+}
\ No newline at end of file