shithub: openh264

Download patch

ref: 3f28643f3ce422d98f1e1b80afc88415cde79005
parent: e38825402ccfa51523b41666400152c1e0437f17
parent: 16a9ed8bd785bc94593ad3f1d71e3f84c441e173
author: huili2 <huili2@cisco.com>
date: Mon Dec 2 04:14:24 EST 2019

Merge pull request #3212 from xiaotianshi2/thread_unit_test_good

Add thread decoding unit test.

--- a/codec/console/dec/src/h264dec.cpp
+++ b/codec/console/dec/src/h264dec.cpp
@@ -142,7 +142,6 @@
     pData[2] = NULL;
     memset (&sDstBufInfo, 0, sizeof (SBufferInfo));
     sDstBufInfo.uiInBsTimeStamp = uiTimeStamp;
-    sDstBufInfo.iBufferStatus = 1;
     pDecoder->FlushFrame (pData, &sDstBufInfo);
     if (sDstBufInfo.iBufferStatus == 1) {
       pDst[0] = sDstBufInfo.pDst[0];
--- a/codec/decoder/core/inc/decoder_context.h
+++ b/codec/decoder/core/inc/decoder_context.h
@@ -538,7 +538,7 @@
   PPicture pDec;
   SWelsDecEvent sImageReady;
   SWelsDecEvent sSliceDecodeStart;
-  SWelsDecEvent sSliceDecodeFinsh;
+  SWelsDecEvent sSliceDecodeFinish;
   int32_t       iPicBuffIdx; //picBuff Index
 } SWelsDecoderThreadCTX, *PWelsDecoderThreadCTX;
 
--- a/codec/decoder/core/src/decode_slice.cpp
+++ b/codec/decoder/core/src/decode_slice.cpp
@@ -1719,7 +1719,16 @@
 
       return ERR_INFO_MB_RECON_FAIL;
     }
+    int8_t pNzc[24];
+    if (pCtx->eSliceType != I_SLICE) {
+      memcpy (pNzc, pCurDqLayer->pNzc[pCurDqLayer->iMbXyIndex], 24);
+      pCtx->sBlockFunc.pWelsSetNonZeroCountFunc (
+        pCurDqLayer->pNzc[pCurDqLayer->iMbXyIndex]); // set all none-zero nzc to 1; dbk can be opti!
+    }
     WelsDeblockingFilterMB (pCurDqLayer, pFilter, iFilterIdc, pDeblockMb);
+    if (pCtx->eSliceType != I_SLICE) {
+      memcpy (pCurDqLayer->pNzc[pCurDqLayer->iMbXyIndex], pNzc, 24);
+    }
     if (pCtx->uiNalRefIdc > 0) {
       if (pCurDqLayer->iMbX == 0 || pCurDqLayer->iMbX == pCurDqLayer->iMbWidth - 1 || pCurDqLayer->iMbY == 0
           || pCurDqLayer->iMbY == pCurDqLayer->iMbHeight - 1) {
--- a/codec/decoder/core/src/decoder_core.cpp
+++ b/codec/decoder/core/src/decoder_core.cpp
@@ -2530,7 +2530,7 @@
       pSh = &pNalCur->sNalData.sVclNal.sSliceHeaderExt.sSliceHeader;
       if (pSh->iFirstMbInSlice == 0) {
         if (pLastThreadCtx->pCtx->pDec != NULL && pLastThreadCtx->pCtx->pDec->bIsUngroupedMultiSlice) {
-          WAIT_EVENT (&pLastThreadCtx->sSliceDecodeFinsh, WELS_DEC_THREAD_WAIT_INFINITE);
+          WAIT_EVENT (&pLastThreadCtx->sSliceDecodeFinish, WELS_DEC_THREAD_WAIT_INFINITE);
         }
         pCtx->pDec = NULL;
         pCtx->iTotalNumMbRec = 0;
@@ -2537,7 +2537,7 @@
       } else if (pLastThreadCtx->pCtx->pDec != NULL) {
         if (pSh->iFrameNum == pLastThreadCtx->pCtx->pDec->iFrameNum
             && pSh->iPicOrderCntLsb == pLastThreadCtx->pCtx->pDec->iFramePoc) {
-          WAIT_EVENT (&pLastThreadCtx->sSliceDecodeFinsh, WELS_DEC_THREAD_WAIT_INFINITE);
+          WAIT_EVENT (&pLastThreadCtx->sSliceDecodeFinish, WELS_DEC_THREAD_WAIT_INFINITE);
           pCtx->pDec = pLastThreadCtx->pCtx->pDec;
           pCtx->pDec->bIsUngroupedMultiSlice = true;
           pCtx->sRefPic = pLastThreadCtx->pCtx->sRefPic;
@@ -2822,12 +2822,9 @@
       if (iThreadCount >= 1) {
         int32_t  id = pThreadCtx->sThreadInfo.uiThrNum;
         for (int32_t i = 0; i < iThreadCount; ++i) {
-          if (i != id) {
-            if (pThreadCtx[i - id].sSliceDecodeStart.isSignaled) {
-              while (pThreadCtx[i - id].pCtx->uiDecodingTimeStamp < pCtx->uiDecodingTimeStamp) {
-                WelsSleep (1);
-              }
-            }
+          if (i == id || pThreadCtx[i - id].pCtx->uiDecodingTimeStamp == 0) continue;
+          if (pThreadCtx[i - id].pCtx->uiDecodingTimeStamp < pCtx->uiDecodingTimeStamp) {
+            WAIT_EVENT (&pThreadCtx[i - id].sSliceDecodeFinish, WELS_DEC_THREAD_WAIT_INFINITE);
           }
         }
         pCtx->pLastDecPicInfo->uiDecodingTimeStamp = pCtx->uiDecodingTimeStamp;
@@ -2835,7 +2832,7 @@
       iRet = DecodeFrameConstruction (pCtx, ppDst, pDstInfo);
       if (iRet) {
         if (iThreadCount > 1) {
-          SET_EVENT (&pThreadCtx->sSliceDecodeFinsh);
+          SET_EVENT (&pThreadCtx->sSliceDecodeFinish);
         }
         return iRet;
       }
@@ -2892,9 +2889,9 @@
         }
       }
     }
-  }
-  if (iThreadCount > 1) {
-    SET_EVENT (&pThreadCtx->sSliceDecodeFinsh);
+    if (iThreadCount > 1) {
+      SET_EVENT (&pThreadCtx->sSliceDecodeFinish);
+    }
   }
   return ERR_NONE;
 }
--- a/codec/decoder/plus/inc/welsDecoderExt.h
+++ b/codec/decoder/plus/inc/welsDecoderExt.h
@@ -127,6 +127,7 @@
   int32_t                 m_DecCtxActiveCount;
   PWelsDecoderThreadCTX   m_pDecThrCtx;
   PWelsDecoderThreadCTX   m_pLastDecThrCtx;
+  int32_t                 m_iLastBufferedIdx;
   WELS_MUTEX              m_csDecoder;
   SWelsDecEvent           m_sBufferingEvent;
   SWelsDecEvent           m_sReleaseBufferEvent;
--- a/codec/decoder/plus/src/welsDecoderExt.cpp
+++ b/codec/decoder/plus/src/welsDecoderExt.cpp
@@ -106,7 +106,7 @@
   }
   pThrCtx->pDec = NULL;
   if (GetThreadCount (pThrCtx->pCtx) > 1) {
-    RESET_EVENT (&pThrCtx->sSliceDecodeFinsh);
+    RESET_EVENT (&pThrCtx->sSliceDecodeFinish);
   }
   iRet |= pWelsDecoder->DecodeFrame2WithCtx (pThrCtx->pCtx, NULL, 0, pThrCtx->ppDst, &pThrCtx->sDstInfo);
 
@@ -142,7 +142,8 @@
     m_bFreezeOutput (false),
     m_DecCtxActiveCount (0),
     m_pDecThrCtx (NULL),
-    m_pLastDecThrCtx (NULL) {
+    m_pLastDecThrCtx (NULL),
+    m_iLastBufferedIdx (0) {
 #ifdef OUTPUT_BIT_STREAM
   char chFileName[1024] = { 0 };  //for .264
   int iBufUsed = 0;
@@ -314,7 +315,7 @@
       m_pDecThrCtx[i].pDec = NULL;
       CREATE_EVENT (&m_pDecThrCtx[i].sImageReady, 1, 0, NULL);
       CREATE_EVENT (&m_pDecThrCtx[i].sSliceDecodeStart, 1, 0, NULL);
-      CREATE_EVENT (&m_pDecThrCtx[i].sSliceDecodeFinsh, 1, 0, NULL);
+      CREATE_EVENT (&m_pDecThrCtx[i].sSliceDecodeFinish, 1, 0, NULL);
       CREATE_SEMAPHORE (&m_pDecThrCtx[i].sThreadInfo.sIsIdle, 0, 1, NULL);
       CREATE_SEMAPHORE (&m_pDecThrCtx[i].sThreadInfo.sIsActivated, 0, 1, NULL);
       CREATE_THREAD (&m_pDecThrCtx[i].sThreadInfo.sThrHandle, pThrProcInit, (void*) (& (m_pDecThrCtx[i])));
@@ -330,7 +331,7 @@
       WAIT_THREAD (&m_pDecThrCtx[i].sThreadInfo.sThrHandle);
       CLOSE_EVENT (&m_pDecThrCtx[i].sImageReady);
       CLOSE_EVENT (&m_pDecThrCtx[i].sSliceDecodeStart);
-      CLOSE_EVENT (&m_pDecThrCtx[i].sSliceDecodeFinsh);
+      CLOSE_EVENT (&m_pDecThrCtx[i].sSliceDecodeFinish);
       CLOSE_SEMAPHORE (&m_pDecThrCtx[i].sThreadInfo.sIsIdle);
       CLOSE_SEMAPHORE (&m_pDecThrCtx[i].sThreadInfo.sIsActivated);
     }
@@ -925,15 +926,43 @@
   }
   if (bEndOfStreamFlag && m_sReoderingStatus.iNumOfPicts > 0) {
     m_sReoderingStatus.iMinPOC = IMinInt32;
-    for (int32_t i = 0; i <= m_sReoderingStatus.iLargestBufferedPicIndex; ++i) {
-      if (m_sReoderingStatus.iMinPOC == IMinInt32 && m_sPictInfoList[i].iPOC > IMinInt32) {
-        m_sReoderingStatus.iMinPOC = m_sPictInfoList[i].iPOC;
-        m_sReoderingStatus.iPictInfoIndex = i;
+    if (m_bIsBaseline) {
+      uint32_t uiDecodingTimeStamp = 0;
+      int32_t firstValidIdx = -1;
+      for (int32_t i = 0; i <= m_sReoderingStatus.iLargestBufferedPicIndex; ++i) {
+        if (m_sPictInfoList[i].iPOC > IMinInt32) {
+          uiDecodingTimeStamp = m_sPictInfoList[i].uiDecodingTimeStamp;
+          m_sReoderingStatus.iMinPOC = m_sPictInfoList[i].iPOC;
+          m_sReoderingStatus.iPictInfoIndex = i;
+          firstValidIdx = i;
+          break;
+        }
       }
-      if (m_sPictInfoList[i].iPOC > IMinInt32 && m_sPictInfoList[i].iPOC < m_sReoderingStatus.iMinPOC) {
-        m_sReoderingStatus.iMinPOC = m_sPictInfoList[i].iPOC;
-        m_sReoderingStatus.iPictInfoIndex = i;
+      for (int32_t i = 0; i <= m_sReoderingStatus.iLargestBufferedPicIndex; ++i) {
+        if (i == firstValidIdx) continue;
+        if (m_sPictInfoList[i].iPOC > IMinInt32 && m_sPictInfoList[i].uiDecodingTimeStamp < uiDecodingTimeStamp) {
+          uiDecodingTimeStamp = m_sPictInfoList[i].uiDecodingTimeStamp;
+          m_sReoderingStatus.iMinPOC = m_sPictInfoList[i].iPOC;
+          m_sReoderingStatus.iPictInfoIndex = i;
+        }
       }
+    } else {
+      int32_t firstValidIdx = -1;
+      for (int32_t i = 0; i <= m_sReoderingStatus.iLargestBufferedPicIndex; ++i) {
+        if (m_sReoderingStatus.iMinPOC == IMinInt32 && m_sPictInfoList[i].iPOC > IMinInt32) {
+          m_sReoderingStatus.iMinPOC = m_sPictInfoList[i].iPOC;
+          m_sReoderingStatus.iPictInfoIndex = i;
+          firstValidIdx = i;
+          break;
+        }
+      }
+      for (int32_t i = 0; i <= m_sReoderingStatus.iLargestBufferedPicIndex; ++i) {
+        if (i == firstValidIdx) continue;
+        if (m_sPictInfoList[i].iPOC > IMinInt32 && m_sPictInfoList[i].iPOC < m_sReoderingStatus.iMinPOC) {
+          m_sReoderingStatus.iMinPOC = m_sPictInfoList[i].iPOC;
+          m_sReoderingStatus.iPictInfoIndex = i;
+        }
+      }
     }
   }
   if (m_sReoderingStatus.iMinPOC > IMinInt32) {
@@ -940,7 +969,8 @@
     m_sReoderingStatus.iLastWrittenPOC = m_sReoderingStatus.iMinPOC;
 #if defined (_DEBUG)
 #ifdef _MOTION_VECTOR_DUMP_
-    fprintf (stderr, "Output POC: #%d\n", m_sReoderingStatus.iLastWrittenPOC);
+    fprintf (stderr, "Output POC: #%d uiDecodingTimeStamp=%d\n", m_sReoderingStatus.iLastWrittenPOC,
+             m_sPictInfoList[m_sReoderingStatus.iPictInfoIndex].uiDecodingTimeStamp);
 #endif
 #endif
     memcpy (pDstInfo, &m_sPictInfoList[m_sReoderingStatus.iPictInfoIndex].sBufferInfo, sizeof (SBufferInfo));
@@ -1051,6 +1081,7 @@
       m_sPictInfoList[i].iPicBuffIdx = pCtx->pLastDecPicInfo->pPreviousDecodedPictureInDpb->iPicBuffIdx;
       if (GetThreadCount (pCtx) <= 1) ++pCtx->pLastDecPicInfo->pPreviousDecodedPictureInDpb->iRefCount;
       m_sPictInfoList[i].bLastGOP = false;
+      m_iLastBufferedIdx = i;
       pDstInfo->iBufferStatus = 0;
       ++m_sReoderingStatus.iNumOfPicts;
       if (i > m_sReoderingStatus.iLargestBufferedPicIndex) {
@@ -1069,11 +1100,17 @@
   }
   if (!m_bIsBaseline && m_sReoderingStatus.iLastGOPRemainPicts > 0) {
     m_sReoderingStatus.iMinPOC = IMinInt32;
+    int32_t firstValidIdx = -1;
     for (int32_t i = 0; i <= m_sReoderingStatus.iLargestBufferedPicIndex; ++i) {
       if (m_sReoderingStatus.iMinPOC == IMinInt32 && m_sPictInfoList[i].iPOC > IMinInt32 && m_sPictInfoList[i].bLastGOP) {
         m_sReoderingStatus.iMinPOC = m_sPictInfoList[i].iPOC;
         m_sReoderingStatus.iPictInfoIndex = i;
+        firstValidIdx = i;
+        break;
       }
+    }
+    for (int32_t i = 0; i <= m_sReoderingStatus.iLargestBufferedPicIndex; ++i) {
+      if (i == firstValidIdx) continue;
       if (m_sPictInfoList[i].iPOC > IMinInt32 && m_sPictInfoList[i].iPOC < m_sReoderingStatus.iMinPOC
           && m_sPictInfoList[i].bLastGOP) {
         m_sReoderingStatus.iMinPOC = m_sPictInfoList[i].iPOC;
@@ -1083,7 +1120,8 @@
     m_sReoderingStatus.iLastWrittenPOC = m_sReoderingStatus.iMinPOC;
 #if defined (_DEBUG)
 #ifdef _MOTION_VECTOR_DUMP_
-    fprintf (stderr, "Output POC: #%d\n", m_sReoderingStatus.iLastWrittenPOC);
+    fprintf (stderr, "Output POC: #%d uiDecodingTimeStamp=%d\n", m_sReoderingStatus.iLastWrittenPOC,
+             m_sPictInfoList[m_sReoderingStatus.iPictInfoIndex].uiDecodingTimeStamp);
 #endif
 #endif
     memcpy (pDstInfo, &m_sPictInfoList[m_sReoderingStatus.iPictInfoIndex].sBufferInfo, sizeof (SBufferInfo));
@@ -1104,21 +1142,29 @@
   }
   if (m_sReoderingStatus.iNumOfPicts && m_bIsBaseline) {
     uint32_t uiDecodingTimeStamp = 0;
+    int32_t firstValidIdx = -1;
     for (int32_t i = 0; i <= m_sReoderingStatus.iLargestBufferedPicIndex; ++i) {
       if (m_sPictInfoList[i].iPOC > IMinInt32) {
         uiDecodingTimeStamp = m_sPictInfoList[i].uiDecodingTimeStamp;
         m_sReoderingStatus.iPictInfoIndex = i;
+        firstValidIdx = i;
         break;
       }
     }
     for (int32_t i = 0; i <= m_sReoderingStatus.iLargestBufferedPicIndex; ++i) {
-      if (m_sReoderingStatus.iPictInfoIndex != i && m_sPictInfoList[i].iPOC > IMinInt32
-          && m_sPictInfoList[i].sBufferInfo.uiInBsTimeStamp < uiDecodingTimeStamp) {
+      if (i == firstValidIdx) continue;
+      if (m_sPictInfoList[i].iPOC > IMinInt32 && m_sPictInfoList[i].uiDecodingTimeStamp < uiDecodingTimeStamp) {
         uiDecodingTimeStamp = m_sPictInfoList[i].uiDecodingTimeStamp;
         m_sReoderingStatus.iPictInfoIndex = i;
       }
     }
     if (uiDecodingTimeStamp > 0) {
+#if defined (_DEBUG)
+#ifdef _MOTION_VECTOR_DUMP_
+      fprintf (stderr, "Output POC: #%d uiDecodingTimeStamp=%d\n", m_sPictInfoList[m_sReoderingStatus.iPictInfoIndex].iPOC,
+               uiDecodingTimeStamp);
+#endif
+#endif
       memcpy (pDstInfo, &m_sPictInfoList[m_sReoderingStatus.iPictInfoIndex].sBufferInfo, sizeof (SBufferInfo));
       ppDst[0] = pDstInfo->pDst[0];
       ppDst[1] = pDstInfo->pDst[1];
@@ -1132,11 +1178,17 @@
   }
   if (m_sReoderingStatus.iNumOfPicts > 0) {
     m_sReoderingStatus.iMinPOC = IMinInt32;
+    int32_t firstValidIdx = -1;
     for (int32_t i = 0; i <= m_sReoderingStatus.iLargestBufferedPicIndex; ++i) {
       if (m_sReoderingStatus.iMinPOC == IMinInt32 && m_sPictInfoList[i].iPOC > IMinInt32) {
         m_sReoderingStatus.iMinPOC = m_sPictInfoList[i].iPOC;
         m_sReoderingStatus.iPictInfoIndex = i;
+        firstValidIdx = i;
+        break;
       }
+    }
+    for (int32_t i = 0; i <= m_sReoderingStatus.iLargestBufferedPicIndex; ++i) {
+      if (i == firstValidIdx) continue;
       if (m_sPictInfoList[i].iPOC > IMinInt32 && m_sPictInfoList[i].iPOC < m_sReoderingStatus.iMinPOC) {
         m_sReoderingStatus.iMinPOC = m_sPictInfoList[i].iPOC;
         m_sReoderingStatus.iPictInfoIndex = i;
@@ -1144,20 +1196,16 @@
     }
   }
   if (m_sReoderingStatus.iMinPOC > IMinInt32) {
-    bool isReady = false;
-    if (pCtx != NULL) {
-      isReady = (m_sReoderingStatus.iLastWrittenPOC > IMinInt32
-                 && m_sReoderingStatus.iMinPOC - m_sReoderingStatus.iLastWrittenPOC <= 1)
-                || m_sReoderingStatus.iMinPOC < pCtx->pSliceHeader->iPicOrderCntLsb;
-    } else {
-      isReady = m_sReoderingStatus.iMinPOC == 0 || (m_sReoderingStatus.iLastWrittenPOC >= 0
-                && m_sReoderingStatus.iMinPOC <= m_sReoderingStatus.iLastWrittenPOC + 2) ;
-    }
+    int32_t iLastPOC = pCtx != NULL ? pCtx->pSliceHeader->iPicOrderCntLsb : m_sPictInfoList[m_iLastBufferedIdx].iPOC;
+    bool isReady = (m_sReoderingStatus.iLastWrittenPOC > IMinInt32
+                    && m_sReoderingStatus.iMinPOC - m_sReoderingStatus.iLastWrittenPOC <= 1)
+                   || m_sReoderingStatus.iMinPOC < iLastPOC;
     if (isReady) {
       m_sReoderingStatus.iLastWrittenPOC = m_sReoderingStatus.iMinPOC;
 #if defined (_DEBUG)
 #ifdef _MOTION_VECTOR_DUMP_
-      fprintf (stderr, "Output POC: #%d\n", m_sReoderingStatus.iLastWrittenPOC);
+      fprintf (stderr, "Output POC: #%d uiDecodingTimeStamp=%d\n", m_sReoderingStatus.iLastWrittenPOC,
+               m_sPictInfoList[m_sReoderingStatus.iPictInfoIndex].uiDecodingTimeStamp);
 #endif
 #endif
       memcpy (pDstInfo, &m_sPictInfoList[m_sReoderingStatus.iPictInfoIndex].sBufferInfo, sizeof (SBufferInfo));
--- /dev/null
+++ b/test/BaseThreadDecoderTest.h
@@ -1,0 +1,61 @@
+#ifndef __BASETHREADDECODERTEST_H__
+#define __BASETHREADDECODERTEST_H__
+
+#include "test_stdint.h"
+#include <limits.h>
+#include <fstream>
+#include "codec_api.h"
+
+#include "utils/BufferedData.h"
+
+class BaseThreadDecoderTest {
+ public:
+  struct Plane {
+    const uint8_t* data;
+    int width;
+    int height;
+    int stride;
+  };
+
+  struct Frame {
+    Plane y;
+    Plane u;
+    Plane v;
+  };
+
+  typedef enum tagDecodeStatus {
+    OpenFile,
+    Decoding,
+    EndOfStream,
+    End
+  } eDecodeStatus;
+
+  struct Callback {
+    virtual void onDecodeFrame (const Frame& frame) = 0;
+  };
+
+  BaseThreadDecoderTest();
+  int32_t SetUp();
+  void TearDown();
+  bool ThreadDecodeFile (const char* fileName, Callback* cbk);
+
+  bool Open (const char* fileName);
+  bool DecodeNextFrame (Callback* cbk);
+  ISVCDecoder* decoder_;
+
+ private:
+  void DecodeFrame (const uint8_t* src, size_t sliceSize, Callback* cbk);
+  void FlushFrame (Callback* cbk);
+
+  std::ifstream file_;
+  BufferedData buf_;
+  BufferedData buf[16];
+  SBufferInfo sBufInfo;
+  uint8_t* pData[3];
+  uint64_t uiTimeStamp;
+  FILE* pYuvFile;
+  bool bEnableYuvDumpTest;
+  eDecodeStatus decodeStatus_;
+};
+
+#endif //__BASETHREADDECODERTEST_H__
--- /dev/null
+++ b/test/api/BaseThreadDecoderTest.cpp
@@ -1,0 +1,359 @@
+#include <fstream>
+#include <gtest/gtest.h>
+#include "codec_def.h"
+#include "codec_app_def.h"
+#include "utils/BufferedData.h"
+#include "BaseThreadDecoderTest.h"
+
+static void Write2File (FILE* pFp, unsigned char* pData[3], int iStride[2], int iWidth, int iHeight) {
+  int   i;
+  unsigned char*  pPtr = NULL;
+
+  pPtr = pData[0];
+  for (i = 0; i < iHeight; i++) {
+    fwrite (pPtr, 1, iWidth, pFp);
+    pPtr += iStride[0];
+  }
+
+  iHeight = iHeight / 2;
+  iWidth = iWidth / 2;
+  pPtr = pData[1];
+  for (i = 0; i < iHeight; i++) {
+    fwrite (pPtr, 1, iWidth, pFp);
+    pPtr += iStride[1];
+  }
+
+  pPtr = pData[2];
+  for (i = 0; i < iHeight; i++) {
+    fwrite (pPtr, 1, iWidth, pFp);
+    pPtr += iStride[1];
+  }
+}
+
+static void Process (SBufferInfo* pInfo, FILE* pFp) {
+  if (pFp && pInfo->pDst[0] && pInfo->pDst[1] && pInfo->pDst[2] && pInfo) {
+    int iStride[2];
+    int iWidth = pInfo->UsrData.sSystemBuffer.iWidth;
+    int iHeight = pInfo->UsrData.sSystemBuffer.iHeight;
+    iStride[0] = pInfo->UsrData.sSystemBuffer.iStride[0];
+    iStride[1] = pInfo->UsrData.sSystemBuffer.iStride[1];
+
+    Write2File (pFp, (unsigned char**)pInfo->pDst, iStride, iWidth, iHeight);
+  }
+}
+
+static bool ReadFrame (std::ifstream* file, BufferedData* buf) {
+  // start code of a frame is {0, 0, 1} or {0, 0, 0, 1}
+  char b;
+
+  buf->Clear();
+  int32_t sps_count = 0;
+  int32_t non_idr_pict_count = 0;
+  int32_t idr_pict_count = 0;
+  int32_t zeroCount = 0;
+  for (;;) {
+    file->read (&b, 1);
+    if (file->gcount() != 1) { // end of file
+      return true;
+    }
+    if (!buf->PushBack (b)) {
+      std::cout << "unable to allocate memory" << std::endl;
+      return false;
+    }
+    if (buf->Length() < 5) {
+      continue;
+    }
+    uint8_t nal_unit_type = 0;
+    int32_t startcode_len_plus_one = 0;
+    if (buf->Length() == 5) {
+      if (buf->data()[2] == 1) {
+        nal_unit_type = buf->data()[3] & 0x1F;
+      } else {
+        nal_unit_type = buf->data()[4] & 0x1F;
+      }
+    } else {
+      if (zeroCount < 2) {
+        zeroCount = b != 0 ? 0 : zeroCount + 1;
+      }
+      if (zeroCount == 2) {
+        file->read (&b, 1);
+        if (file->gcount() != 1) { // end of file
+          return true;
+        }
+        if (!buf->PushBack (b)) {
+          std::cout << "unable to allocate memory" << std::endl;
+          return false;
+        }
+        if (b == 1) { //0x000001
+          file->read (&b, 1);
+          if (file->gcount() != 1) { // end of file
+            return true;
+          }
+          if (!buf->PushBack (b)) {
+            std::cout << "unable to allocate memory" << std::endl;
+            return false;
+          }
+          nal_unit_type = b & 0x1F;
+          startcode_len_plus_one = 4;
+          zeroCount = 0;
+        } else if (b == 0) {
+          file->read (&b, 1);
+          if (file->gcount() != 1) { // end of file
+            return true;
+          }
+          if (!buf->PushBack (b)) {
+            std::cout << "unable to allocate memory" << std::endl;
+            return false;
+          }
+          if (b == 1) { //0x00000001
+            file->read (&b, 1);
+            if (file->gcount() != 1) { // end of file
+              return true;
+            }
+            if (!buf->PushBack (b)) {
+              std::cout << "unable to allocate memory" << std::endl;
+              return false;
+            }
+            nal_unit_type = b & 0x1F;
+            startcode_len_plus_one = 5;
+            zeroCount = 0;
+          } else {
+            zeroCount = 0;
+          }
+        } else {
+          zeroCount = 0;
+        }
+      }
+    }
+    if (nal_unit_type == 1) {
+      if (++non_idr_pict_count == 1 && idr_pict_count == 1) {
+        file->seekg (-startcode_len_plus_one, file->cur).good();
+        buf->SetLength (buf->Length() - startcode_len_plus_one);
+        return true;
+      }
+      if (non_idr_pict_count == 2) {
+        file->seekg (-startcode_len_plus_one, file->cur).good();
+        buf->SetLength (buf->Length() - startcode_len_plus_one);
+        return true;
+      }
+    } else if (nal_unit_type == 5) {
+      if (++idr_pict_count == 1 && non_idr_pict_count == 1) {
+        file->seekg (-startcode_len_plus_one, file->cur).good();
+        buf->SetLength (buf->Length() - startcode_len_plus_one);
+        return true;
+      }
+      if (idr_pict_count == 2) {
+        file->seekg (-startcode_len_plus_one, file->cur).good();
+        buf->SetLength (buf->Length() - startcode_len_plus_one);
+        return true;
+      }
+    } else if (nal_unit_type == 7) {
+      if ((++sps_count == 1) && (non_idr_pict_count == 1 || idr_pict_count == 1)) {
+        file->seekg (-startcode_len_plus_one, file->cur).good();
+        buf->SetLength (buf->Length() - startcode_len_plus_one);
+        return true;
+      }
+    }
+  }
+}
+
+BaseThreadDecoderTest::BaseThreadDecoderTest()
+  : decoder_ (NULL), uiTimeStamp (0), pYuvFile (NULL), bEnableYuvDumpTest (false), decodeStatus_ (OpenFile) {
+}
+
+int32_t BaseThreadDecoderTest::SetUp() {
+  long rv = WelsCreateDecoder (&decoder_);
+  EXPECT_EQ (0, rv);
+  EXPECT_TRUE (decoder_ != NULL);
+  if (decoder_ == NULL) {
+    return rv;
+  }
+
+  SDecodingParam decParam;
+  memset (&decParam, 0, sizeof (SDecodingParam));
+  decParam.uiTargetDqLayer = UCHAR_MAX;
+  decParam.eEcActiveIdc = ERROR_CON_SLICE_COPY;
+  decParam.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_DEFAULT;
+  int iThreadCount = (rand() % 2) + 2;
+  fprintf (stderr, "iThreadCount=%d\n", iThreadCount);
+  decoder_->SetOption (DECODER_OPTION_NUM_OF_THREADS, &iThreadCount);
+
+  rv = decoder_->Initialize (&decParam);
+  EXPECT_EQ (0, rv);
+  return (int32_t)rv;
+}
+
+void BaseThreadDecoderTest::TearDown() {
+  if (decoder_ != NULL) {
+    decoder_->Uninitialize();
+    WelsDestroyDecoder (decoder_);
+  }
+}
+
+
+void BaseThreadDecoderTest::DecodeFrame (const uint8_t* src, size_t sliceSize, Callback* cbk) {
+  SBufferInfo bufInfo;
+  memset (pData, 0, sizeof (pData));
+  memset (&bufInfo, 0, sizeof (SBufferInfo));
+  bufInfo.uiInBsTimeStamp = ++uiTimeStamp;
+
+  DECODING_STATE rv = decoder_->DecodeFrameNoDelay (src, (int) sliceSize, pData, &bufInfo);
+  ASSERT_TRUE (rv == dsErrorFree);
+  sBufInfo = bufInfo;
+  if (sBufInfo.iBufferStatus == 1 && cbk != NULL) {
+    if (bEnableYuvDumpTest) {
+      Process (&sBufInfo, pYuvFile);
+    }
+    const Frame frame = {
+      {
+        // y plane
+        sBufInfo.pDst[0],
+        bufInfo.UsrData.sSystemBuffer.iWidth,
+        bufInfo.UsrData.sSystemBuffer.iHeight,
+        bufInfo.UsrData.sSystemBuffer.iStride[0]
+      },
+      {
+        // u plane
+        sBufInfo.pDst[1],
+        sBufInfo.UsrData.sSystemBuffer.iWidth / 2,
+        sBufInfo.UsrData.sSystemBuffer.iHeight / 2,
+        sBufInfo.UsrData.sSystemBuffer.iStride[1]
+      },
+      {
+        // v plane
+        sBufInfo.pDst[2],
+        sBufInfo.UsrData.sSystemBuffer.iWidth / 2,
+        sBufInfo.UsrData.sSystemBuffer.iHeight / 2,
+        sBufInfo.UsrData.sSystemBuffer.iStride[1]
+      },
+    };
+    cbk->onDecodeFrame (frame);
+  }
+}
+void BaseThreadDecoderTest::FlushFrame (Callback* cbk) {
+  SBufferInfo bufInfo;
+  memset (pData, 0, sizeof (pData));
+  memset (&bufInfo, 0, sizeof (SBufferInfo));
+
+  DECODING_STATE rv = decoder_->FlushFrame (pData, &bufInfo);
+  ASSERT_TRUE (rv == dsErrorFree);
+  sBufInfo = bufInfo;
+  if (sBufInfo.iBufferStatus == 1 && cbk != NULL) {
+    if (bEnableYuvDumpTest) {
+      Process (&sBufInfo, pYuvFile);
+    }
+    const Frame frame = {
+      {
+        // y plane
+        sBufInfo.pDst[0],
+        sBufInfo.UsrData.sSystemBuffer.iWidth,
+        sBufInfo.UsrData.sSystemBuffer.iHeight,
+        sBufInfo.UsrData.sSystemBuffer.iStride[0]
+      },
+      {
+        // u plane
+        sBufInfo.pDst[1],
+        sBufInfo.UsrData.sSystemBuffer.iWidth / 2,
+        sBufInfo.UsrData.sSystemBuffer.iHeight / 2,
+        sBufInfo.UsrData.sSystemBuffer.iStride[1]
+      },
+      {
+        // v plane
+        sBufInfo.pDst[2],
+        sBufInfo.UsrData.sSystemBuffer.iWidth / 2,
+        sBufInfo.UsrData.sSystemBuffer.iHeight / 2,
+        sBufInfo.UsrData.sSystemBuffer.iStride[1]
+      },
+    };
+    cbk->onDecodeFrame (frame);
+  }
+}
+bool BaseThreadDecoderTest::ThreadDecodeFile (const char* fileName, Callback* cbk) {
+  std::ifstream file (fileName, std::ios::in | std::ios::binary);
+  if (!file.is_open())
+    return false;
+
+  std::string outFileName = std::string (fileName);
+  size_t pos = outFileName.find_last_of (".");
+  if (bEnableYuvDumpTest) {
+    outFileName = outFileName.substr (0, pos) + std::string (".yuv");
+    pYuvFile = fopen (outFileName.c_str(), "wb");
+  }
+
+  int iBufIndex = 0;
+  uiTimeStamp = 0;
+  memset (&sBufInfo, 0, sizeof (SBufferInfo));
+  while (true) {
+    if (false == ReadFrame (&file, &buf[iBufIndex]))
+      return false;
+    if (::testing::Test::HasFatalFailure()) {
+      return false;
+    }
+    if (buf[iBufIndex].Length() == 0) {
+      break;
+    }
+    DecodeFrame (buf[iBufIndex].data(), buf[iBufIndex].Length(), cbk);
+    if (::testing::Test::HasFatalFailure()) {
+      return false;
+    }
+    if (++iBufIndex >= 16) {
+      iBufIndex = 0;
+    }
+  }
+
+  int32_t iEndOfStreamFlag = 1;
+  decoder_->SetOption (DECODER_OPTION_END_OF_STREAM, &iEndOfStreamFlag);
+
+  // Flush out last frames in decoder buffer
+  int32_t num_of_frames_in_buffer = 0;
+  decoder_->GetOption (DECODER_OPTION_NUM_OF_FRAMES_REMAINING_IN_BUFFER, &num_of_frames_in_buffer);
+  for (int32_t i = 0; i < num_of_frames_in_buffer; ++i) {
+    FlushFrame (cbk);
+  }
+  if (bEnableYuvDumpTest) {
+    fclose (pYuvFile);
+  }
+  return true;
+}
+
+bool BaseThreadDecoderTest::Open (const char* fileName) {
+  if (decodeStatus_ == OpenFile) {
+    file_.open (fileName, std::ios_base::out | std::ios_base::binary);
+    if (file_.is_open()) {
+      decodeStatus_ = Decoding;
+      return true;
+    }
+  }
+  return false;
+}
+
+bool BaseThreadDecoderTest::DecodeNextFrame (Callback* cbk) {
+  switch (decodeStatus_) {
+  case Decoding:
+    if (false == ReadFrame (&file_, &buf_))
+      return false;
+    if (::testing::Test::HasFatalFailure()) {
+      return false;
+    }
+    if (buf_.Length() == 0) {
+      decodeStatus_ = EndOfStream;
+      return true;
+    }
+    DecodeFrame (buf_.data(), buf_.Length(), cbk);
+    if (::testing::Test::HasFatalFailure()) {
+      return false;
+    }
+    return true;
+  case EndOfStream: {
+    int32_t iEndOfStreamFlag = 1;
+    decoder_->SetOption (DECODER_OPTION_END_OF_STREAM, &iEndOfStreamFlag);
+    DecodeFrame (NULL, 0, cbk);
+    decodeStatus_ = End;
+    break;
+  }
+  case OpenFile:
+  case End:
+    break;
+  }
+  return false;
+}
--- a/test/api/meson.build
+++ b/test/api/meson.build
@@ -1,5 +1,6 @@
 test_sources = [
   'BaseDecoderTest.cpp',
+  'BaseThreadDecoderTest.cpp',
   'BaseEncoderTest.cpp',
   'cpp_interface_test.cpp',
   'DataGenerator.cpp',
@@ -7,6 +8,7 @@
   'decode_encode_test.cpp',
   'decoder_ec_test.cpp',
   'decoder_test.cpp',
+  'thread_decoder_test.cpp',
   'encode_decode_api_test.cpp',
   'encode_options_test.cpp',
   'encoder_test.cpp',
--- a/test/api/targets.mk
+++ b/test/api/targets.mk
@@ -4,6 +4,7 @@
 API_TEST_SRCDIR=test/api
 API_TEST_CPP_SRCS=\
 	$(API_TEST_SRCDIR)/BaseDecoderTest.cpp\
+	$(API_TEST_SRCDIR)/BaseThreadDecoderTest.cpp\
 	$(API_TEST_SRCDIR)/BaseEncoderTest.cpp\
 	$(API_TEST_SRCDIR)/cpp_interface_test.cpp\
 	$(API_TEST_SRCDIR)/DataGenerator.cpp\
@@ -11,6 +12,7 @@
 	$(API_TEST_SRCDIR)/decode_encode_test.cpp\
 	$(API_TEST_SRCDIR)/decoder_ec_test.cpp\
 	$(API_TEST_SRCDIR)/decoder_test.cpp\
+	$(API_TEST_SRCDIR)/thread_decoder_test.cpp\
 	$(API_TEST_SRCDIR)/encode_decode_api_test.cpp\
 	$(API_TEST_SRCDIR)/encode_options_test.cpp\
 	$(API_TEST_SRCDIR)/encoder_test.cpp\
--- /dev/null
+++ b/test/api/thread_decoder_test.cpp
@@ -1,0 +1,153 @@
+#include <gtest/gtest.h>
+#include "utils/HashFunctions.h"
+#include "BaseThreadDecoderTest.h"
+#include <string>
+
+static void UpdateHashFromPlane (SHA1Context* ctx, const uint8_t* plane,
+                                 int width, int height, int stride) {
+  for (int i = 0; i < height; i++) {
+    SHA1Input (ctx, plane, width);
+    plane += stride;
+  }
+}
+
+class ThreadDecoderCapabilityTest : public ::testing::Test {
+ public:
+  virtual void SetUp() {}
+  virtual void TearDown() {}
+};
+
+TEST_F (ThreadDecoderCapabilityTest, JustInit) {
+  SDecoderCapability sDecCap;
+  int iRet = WelsGetDecoderCapability (&sDecCap);
+  ASSERT_TRUE (iRet == 0);
+  EXPECT_EQ (sDecCap.iProfileIdc, 66);
+  EXPECT_EQ (sDecCap.iProfileIop, 0xE0);
+  EXPECT_EQ (sDecCap.iLevelIdc, 32);
+  EXPECT_EQ (sDecCap.iMaxMbps, 216000);
+  EXPECT_EQ (sDecCap.iMaxFs, 5120);
+  EXPECT_EQ (sDecCap.iMaxCpb, 20000);
+  EXPECT_EQ (sDecCap.iMaxDpb, 20480);
+  EXPECT_EQ (sDecCap.iMaxBr, 20000);
+  EXPECT_EQ (sDecCap.bRedPicCap, false);
+}
+
+
+class ThreadDecoderInitTest : public ::testing::Test, public BaseThreadDecoderTest {
+ public:
+  virtual void SetUp() {
+    BaseThreadDecoderTest::SetUp();
+  }
+  virtual void TearDown() {
+    BaseThreadDecoderTest::TearDown();
+  }
+};
+
+TEST_F (ThreadDecoderInitTest, JustInit) {}
+struct FileParam {
+  const char* fileName;
+  const char* hashStr;
+};
+
+class ThreadDecoderOutputTest : public ::testing::WithParamInterface<FileParam>,
+  public ThreadDecoderInitTest, public BaseThreadDecoderTest::Callback {
+ public:
+  virtual void SetUp() {
+    ThreadDecoderInitTest::SetUp();
+    if (HasFatalFailure()) {
+      return;
+    }
+    SHA1Reset (&ctx_);
+  }
+  virtual void onDecodeFrame (const Frame& frame) {
+    const Plane& y = frame.y;
+    const Plane& u = frame.u;
+    const Plane& v = frame.v;
+    UpdateHashFromPlane (&ctx_, y.data, y.width, y.height, y.stride);
+    UpdateHashFromPlane (&ctx_, u.data, u.width, u.height, u.stride);
+    UpdateHashFromPlane (&ctx_, v.data, v.width, v.height, v.stride);
+  }
+ protected:
+  SHA1Context ctx_;
+};
+
+TEST_P (ThreadDecoderOutputTest, CompareOutput) {
+  FileParam p = GetParam();
+#if defined(ANDROID_NDK)
+  std::string filename = std::string ("/sdcard/") + p.fileName;
+  ASSERT_TRUE (ThreadDecodeFile (filename.c_str(), this));
+#else
+  ASSERT_TRUE (ThreadDecodeFile (p.fileName, this));
+#endif
+
+  unsigned char digest[SHA_DIGEST_LENGTH];
+  SHA1Result (&ctx_, digest);
+  if (!HasFatalFailure()) {
+    std::string p_hashStr(p.hashStr);
+    std::stringstream ss(p_hashStr);
+    std::string buf[4];
+    const char * hashStr[4];
+    int i = 0;
+    while (i < 4 && ss >> buf[i]) {
+      hashStr[i] = buf[i].c_str();
+      ++i;
+    }
+    CompareHashAnyOf (digest, hashStr, i);
+  }
+}
+static const FileParam kFileParamArray[] = {
+  {"res/Adobe_PDF_sample_a_1024x768_50Frms.264", "041434a5819d1d903d49c0eda884b345e9f83596"},
+  {"res/BA1_FT_C.264", "48d65bf8c731f29efc72f6222dd85b8ef7f636a7 463284ff3f5a0b1a0c829d47e9b73dcfe617c926"},
+  {"res/BA1_Sony_D.jsv", "37c9a951a0348d6abe1880b59e2b5a4d7d18c94c"},
+  {"res/BAMQ1_JVC_C.264", "6720462624f632f5475716ef32a7bbd12b3b428a 477b1e45e30661a138ff0b43c1ed3e00ded13d9c"},
+  {"res/BAMQ2_JVC_C.264", "5f0fbb0dab7961e782224f6887c83d4866fc1af8 e3dfdc770fa5fee8b92f896a92214886c109a688"},
+  {"res/BA_MW_D.264", "ace02cdce720bdb0698b40dc749a0e61fe0f590b"},
+  {"res/BANM_MW_D.264", "c51f1d2fa63dba4f5787f1b726c056d1c01d6ab9"},
+  {"res/BASQP1_Sony_C.jsv", "68e604b77e3f57f8ef1c2e450fcef03f5d2aee90 d5e1f122e8bf8d58bc6775d69b837db0d9ea3454"},
+  {"res/CI1_FT_B.264", "96042b70e212c8b253a786e865131ac89c133ca1 53d2a2f276a81b6ef869791373f7bc3928cc9ca3"},
+  {"res/CI_MW_D.264", "49a8916edd3e571efad328f2784fbe6aec5570d7"},
+  {"res/CVFC1_Sony_C.jsv", "109dfc8357a98b16aa74469a5506e362e563aa85 f29da8955c6f01d972dfe10b22c4f879aab05412"},
+  {"res/CVPCMNL1_SVA_C.264", "c2b0d964de727c64b9fccb58f63b567c82bda95a"},
+  //{"res/LS_SVA_D.264", "72118f4d1674cf14e58bed7e67cb3aeed3df62b9"}, //DPB buffer is too small
+  {"res/MIDR_MW_D.264", "aeded2be7b97484cbf25f367ec34208f2220a8ab"},
+  {"res/MPS_MW_A.264", "b0fce28218e678d89f464810f88b143ada49dd06"},
+  //{"res/MR1_BT_A.h264", "eebd1d7cdb67df5b8688b1ce18f6acae129b32e6 d20e96f9ecc2e24c13eb25b1c786da53eb716327"}, three hash values temp disabled
+  {"res/MR1_MW_A.264", "14d8ddb12ed711444039329db29c496b079680ba"},
+  //{"res/MR2_MW_A.264", "6d332a653fe3b923eb3af8f3695d46ce2a1d4b2c e379caa57c0c60ca6d6091c19815c7422e3c59c7 34f0359290b9e83be82ea2f8e763d920ec446b7b 14a38e41f4dbf924b8eff6e96aad77394c8aabcd"},
+  //{"res/MR2_TANDBERG_E.264", "74d618bc7d9d41998edf4c85d51aa06111db6609"}, //DPB buffer is too small
+  {"res/NL1_Sony_D.jsv", "e401e30669938443c2f02522fd4d5aa1382931a0"},
+  {"res/NLMQ1_JVC_C.264", "f3265c6ddf8db1b2bf604d8a2954f75532e28cda a86ec7a843e93f44aaee2619a7932c6c5c8d233f"},
+  {"res/NLMQ2_JVC_C.264", "350ae86ef9ba09390d63a09b7f9ff54184109ca8 95e6e4426b75f38a6744f3d04cfc62a2c0489354"},
+  {"res/NRF_MW_E.264", "866f267afd2ed1595bcb90de0f539e929c169aa4 db2d135cef07db8247ef858daf870d07955b912a"},
+  {"res/QCIF_2P_I_allIPCM.264", "9879ce127d3263cfbaf5211ab6657dbf0ccabea8"},
+  { "res/SVA_BA1_B.264", "4cb45a99ae44a0a98b174efd66245daa1fbaeb47 e9127875b268f9e7da4c495799b9972b8e72cf7b"},
+  {"res/SVA_BA2_D.264", "ac9e960015b96f83279840802f6637c61ee1c5b8 719fe839fa68b915b614fbbbae15edf492cc2133"},
+  {"res/SVA_Base_B.264", "6015682a8f957bd499242150315cfd2fdf23e728 cd8c8ad018fe2fd7b3893f8175265b0eb7bcd342"},
+  {"res/SVA_CL1_E.264", "4fe09ab6cdc965ea10a20f1d6dd38aca954412bb"},
+  {"res/SVA_FM1_E.264", "164a9a2db58e7200ae60db8079d7201ac431887a fb33868ae38b3642edd309ad3005de4214fcc63f"},
+  {"res/SVA_NL1_B.264", "6d63f72a0c0d833b1db0ba438afff3b4180fb3e6"},
+  {"res/SVA_NL2_E.264", "70453ef8097c94dd190d6d2d1d5cb83c67e66238"},
+  //{"res/SarVui.264", "1843d19d8e13588ef5de2d647804ae141e55cf72 719fe839fa68b915b614fbbbae15edf492cc2133"}, //same as "res/SVA_BA1_B.264"
+  {"res/Static.264", "d865faee7df56a8f532b7baeacb814483b8be148 52af285a888b8c9e04dc9f38fd61105e805ada3a"},
+  {"res/Zhling_1280x720.264", "10f9c803e80b51786f7833255afc3ef75c5c1339"},
+  {"res/sps_subsetsps_bothVUI.264", "d65a34075c452196401340c554e83225c9454397"},
+  //{"res/test_cif_I_CABAC_PCM.264", "dfe2f87ac76bdb58e227267907a2eeccf04715ad 02ac993be06b5d88118beb96ee5dfd0995b7cb00 95fdf21470d3bbcf95505abb2164042063a79d98 c2b42f489ca9c2ebc43c0ab2238551a0c958a692"},
+  {"res/test_cif_I_CABAC_slice.264", "4260cc7a211895341092b0361bcfc3f13721ab44 106da52c2c6d30255b6ac0aa0b4a881a06ebb762"},
+  //{"res/test_cif_P_CABAC_slice.264", "ac2d1e9ca0e097ab44a4b592a93e06e5c0c3d761 276a5ccef4bbe20ad9c769824aea5553acc7b54a 8ba773ccf5f682a4a90b0d070aa4198a5cfa0220 b09e066f797235fed8f59c408b5914d143f71c9e"},
+  {"res/test_qcif_cabac.264", "c79e9a32e4d9e38a1bd12079da19dcb0d2efe539"},
+  {"res/test_scalinglist_jm.264", "b36efd05c8b17faa23f1c071b92aa5d55a5a826f"},
+  {"res/test_vd_1d.264", "15d8beaf991f9e5d56a854cdafc0a7abdd5bec69"},
+  {"res/test_vd_rc.264", "cd6ef57fc884e5ecd9867591b01e35e3f091b8d0"},
+  {"res/Cisco_Men_whisper_640x320_CABAC_Bframe_9.264", "7df59855104a319b44a7611dd6c37b1670bf74c9"},
+  {"res/Cisco_Men_whisper_640x320_CAVLC_Bframe_9.264", "0d77e3c53f46d8962cd95b975e76d0f32613da0f"},
+  {"res/Cisco_Adobe_PDF_sample_a_1024x768_CAVLC_Bframe_9.264", "6cac61a6b58bba59b8e9944b18aba2df20efeca2"},
+  {"res/VID_1280x544_cabac_temporal_direct.264", "e8ee8dd56ec5df1338f3c21ed8690d074c7ec03f"},
+  {"res/VID_1280x720_cabac_temporal_direct.264", "1efa6aec8c5f953c53d713c31999420fdbd10b22"},
+  {"res/VID_1920x1080_cabac_temporal_direct.264", "90b3f1cf0c85b490108a2db40d2b2151ee346dfb aafd2606e8fe8be2a956deed48218c9f5176b3d0"},
+  {"res/VID_1280x544_cavlc_temporal_direct.264", "fe779025f3b42d6fc3590476cb3594540950d716"},
+  {"res/VID_1280x720_cavlc_temporal_direct.264", "1c5afab7cfeb082b087821d4220d57238c1c161f"},
+  {"res/VID_1920x1080_cavlc_temporal_direct.264", "5c47d30fed9d2988c653b2c3bc83f6d19dfa5ab1 eecd84b68f416270eb21c6c90a4cef8603d37e25"},
+};
+
+INSTANTIATE_TEST_CASE_P (ThreadDecodeFile, ThreadDecoderOutputTest,
+                         ::testing::ValuesIn (kFileParamArray));
--- a/test/build/win32/codec_ut/codec_unittest.vcproj
+++ b/test/build/win32/codec_ut/codec_unittest.vcproj
@@ -331,6 +331,10 @@
 				>
 			</File>
 			<File
+				RelativePath="..\..\..\api\BaseThreadDecoderTest.cpp"
+				>
+			</File>
+			<File
 				RelativePath="..\..\..\api\BaseEncoderTest.cpp"
 				>
 			</File>
@@ -360,6 +364,10 @@
 			</File>
 			<File
 				RelativePath="..\..\..\api\decoder_test.cpp"
+				>
+			</File>
+			<File
+				RelativePath="..\..\..\api\thread_decoder_test.cpp"
 				>
 			</File>
 			<File