shithub: openh264

Download patch

ref: 33a1209a21a9b38eafc5f26bb6543c8b4c9b75ba
parent: db6ce9c3d8692b414f2e0ace8843578b91d58385
parent: f2aaffbb72aa3745855ca43f7674802a70bf2282
author: Ethan Hugg <ethanhugg@gmail.com>
date: Wed Feb 5 14:43:07 EST 2014

Merge pull request #264 from jwwang/add_gtests

Add gtests

--- /dev/null
+++ b/test/BaseDecoderTest.cpp
@@ -1,0 +1,171 @@
+#include <fstream>
+#include <gtest/gtest.h>
+#include "codec_def.h"
+#include "codec_app_def.h"
+#include "utils/BufferedData.h"
+#include "BaseDecoderTest.h"
+
+static void ReadFrame(std::ifstream* file, BufferedData* buf) {
+  // start code of a frame is {0, 0, 0, 1}
+  int zeroCount = 0;
+  char b;
+
+  buf->Clear();
+  for (;;) {
+    file->read(&b, 1);
+    if (file->gcount() != 1) { // end of file
+      return;
+    }
+    if (!buf->PushBack(b)) {
+      FAIL() << "unable to allocate memory";
+    }
+
+    if (buf->Length() <= 4) {
+      continue;
+    }
+
+    if (zeroCount < 3) {
+      zeroCount = b != 0 ? 0 : zeroCount + 1;
+    } else {
+      if (b == 1) {
+        if (file->seekg(-4, file->cur).good()) {
+          buf->SetLength(buf->Length() - 4);
+          return;
+        } else {
+          FAIL() << "unable to seek file";
+        }
+      } else if (b == 0) {
+        zeroCount = 3;
+      } else {
+        zeroCount = 0;
+      }
+    }
+  }
+}
+
+BaseDecoderTest::BaseDecoderTest()
+  : decoder_(NULL), decodeStatus_(OpenFile) {}
+
+void BaseDecoderTest::SetUp() {
+  long rv = CreateDecoder(&decoder_);
+  ASSERT_EQ(0, rv);
+  ASSERT_TRUE(decoder_ != NULL);
+
+  SDecodingParam decParam;
+  memset(&decParam, 0, sizeof(SDecodingParam));
+  decParam.iOutputColorFormat  = videoFormatI420;
+  decParam.uiTargetDqLayer = UCHAR_MAX;
+  decParam.uiEcActiveFlag  = 1;
+  decParam.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_DEFAULT;
+
+  rv = decoder_->Initialize(&decParam, INIT_TYPE_PARAMETER_BASED);
+  ASSERT_EQ(0, rv);
+}
+
+void BaseDecoderTest::TearDown() {
+  if (decoder_ != NULL) {
+    decoder_->Uninitialize();
+    DestroyDecoder(decoder_);
+  }
+}
+
+
+void BaseDecoderTest::DecodeFrame(const uint8_t* src, int sliceSize, Callback* cbk) {
+  void* data[3];
+  SBufferInfo bufInfo;
+  memset(data, 0, sizeof(data));
+  memset(&bufInfo, 0, sizeof(SBufferInfo));
+
+  DECODING_STATE rv = decoder_->DecodeFrame2(src, sliceSize, data, &bufInfo);
+  ASSERT_TRUE(rv == dsErrorFree);
+
+  if (bufInfo.iBufferStatus == 1 && cbk != NULL) {
+    const Frame frame = {
+        { // y plane
+            static_cast<uint8_t*>(data[0]),
+            bufInfo.UsrData.sSystemBuffer.iWidth,
+            bufInfo.UsrData.sSystemBuffer.iHeight,
+            bufInfo.UsrData.sSystemBuffer.iStride[0]
+        },
+        { // u plane
+            static_cast<uint8_t*>(data[1]),
+            bufInfo.UsrData.sSystemBuffer.iWidth / 2,
+            bufInfo.UsrData.sSystemBuffer.iHeight / 2,
+            bufInfo.UsrData.sSystemBuffer.iStride[1]
+        },
+        { // v plane
+            static_cast<uint8_t*>(data[2]),
+            bufInfo.UsrData.sSystemBuffer.iWidth / 2,
+            bufInfo.UsrData.sSystemBuffer.iHeight / 2,
+            bufInfo.UsrData.sSystemBuffer.iStride[1]
+        },
+    };
+    cbk->onDecodeFrame(frame);
+  }
+}
+void BaseDecoderTest::DecodeFile(const char* fileName, Callback* cbk) {
+  std::ifstream file(fileName, std::ios::in | std::ios::binary);
+  ASSERT_TRUE(file.is_open());
+
+  BufferedData buf;
+  while (true) {
+    ReadFrame(&file, &buf);
+    if (::testing::Test::HasFatalFailure()) {
+      return;
+    }
+    if (buf.Length() == 0) {
+      break;
+    }
+    DecodeFrame(buf.data(), buf.Length(), cbk);
+    if (::testing::Test::HasFatalFailure()) {
+      return;
+    }
+  }
+
+  int32_t iEndOfStreamFlag = 1;
+  decoder_->SetOption(DECODER_OPTION_END_OF_STREAM, &iEndOfStreamFlag);
+
+  // Get pending last frame
+  DecodeFrame(NULL, 0, cbk);
+}
+
+bool BaseDecoderTest::Open(const char* fileName) {
+  if (decodeStatus_ == OpenFile) {
+    file_.open(fileName);
+    if (file_.is_open()) {
+      decodeStatus_ = Decoding;
+      return true;
+    }
+  }
+  return false;
+}
+
+bool BaseDecoderTest::DecodeNextFrame(Callback* cbk) {
+  switch (decodeStatus_) {
+  case Decoding:
+    ReadFrame(&file_, &buf_);
+    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;
+}
--- /dev/null
+++ b/test/BaseDecoderTest.h
@@ -1,0 +1,52 @@
+#ifndef __BASEDECODERTEST_H__
+#define __BASEDECODERTEST_H__
+
+#include <stdint.h>
+#include <limits.h>
+#include <fstream>
+#include "codec_api.h"
+
+#include "utils/BufferedData.h"
+
+class BaseDecoderTest {
+ public:
+  struct Plane {
+    const uint8_t* data;
+    int width;
+    int height;
+    int stride;
+  };
+
+  struct Frame {
+    Plane y;
+    Plane u;
+    Plane v;
+  };
+
+  struct Callback {
+    virtual void onDecodeFrame(const Frame& frame) = 0;
+  };
+
+  BaseDecoderTest();
+  void SetUp();
+  void TearDown();
+  void DecodeFile(const char* fileName, Callback* cbk);
+
+  bool Open(const char* fileName);
+  bool DecodeNextFrame(Callback* cbk);
+
+ private:
+  void DecodeFrame(const uint8_t* src, int sliceSize, Callback* cbk);
+
+  ISVCDecoder* decoder_;
+  std::ifstream file_;
+  BufferedData buf_;
+  enum {
+    OpenFile,
+    Decoding,
+    EndOfStream,
+    End
+  } decodeStatus_;
+};
+
+#endif //__BASEDECODERTEST_H__
--- /dev/null
+++ b/test/BaseEncoderTest.cpp
@@ -1,0 +1,83 @@
+#include <fstream>
+#include <gtest/gtest.h>
+#include "codec_def.h"
+#include "utils/BufferedData.h"
+#include "utils/FileInputStream.h"
+#include "BaseEncoderTest.h"
+
+static int InitWithParam(ISVCEncoder* encoder, int width,
+    int height, float frameRate) {
+  SVCEncodingParam param;
+  memset (&param, 0, sizeof(SVCEncodingParam));
+
+  param.sSpatialLayers[0].iVideoWidth  = width;
+  param.sSpatialLayers[0].iVideoHeight = height;
+  param.sSpatialLayers[0].fFrameRate = frameRate;
+  param.sSpatialLayers[0].iQualityLayerNum = 1;
+  param.sSpatialLayers[0].iSpatialBitrate = 600000;
+
+  SSliceConfig* sliceCfg = &param.sSpatialLayers[0].sSliceCfg;
+  sliceCfg->sSliceArgument.uiSliceNum = 1;
+  sliceCfg->sSliceArgument.uiSliceSizeConstraint = 1500;
+  sliceCfg->sSliceArgument.uiSliceMbNum[0] = 960;
+
+  param.fFrameRate = param.sSpatialLayers[0].fFrameRate;
+  param.iPicWidth = param.sSpatialLayers[0].iVideoWidth;
+  param.iPicHeight = param.sSpatialLayers[0].iVideoHeight;
+  param.iTargetBitrate = 5000000;
+  param.iTemporalLayerNum = 3;
+  param.iSpatialLayerNum = 1;
+  param.bEnableBackgroundDetection = true;
+  param.bEnableLongTermReference = true;
+  param.iLtrMarkPeriod = 30;
+  param.iInputCsp = videoFormatI420;
+  param.bEnableSpsPpsIdAddition = true;
+
+  return encoder->Initialize(&param, INIT_TYPE_PARAMETER_BASED);
+}
+
+BaseEncoderTest::BaseEncoderTest() : encoder_(NULL) {}
+
+void BaseEncoderTest::SetUp() {
+  int rv = CreateSVCEncoder(&encoder_);
+  ASSERT_EQ(0, rv);
+  ASSERT_TRUE(encoder_ != NULL);
+}
+
+void BaseEncoderTest::TearDown() {
+  if (encoder_) {
+    encoder_->Uninitialize();
+    DestroySVCEncoder(encoder_);
+  }
+}
+
+void BaseEncoderTest::EncodeStream(InputStream* in, int width, int height,
+    float frameRate, Callback* cbk) {
+  int rv = InitWithParam(encoder_, width, height, frameRate);
+  ASSERT_TRUE(rv == cmResultSuccess);
+
+  // I420: 1(Y) + 1/4(U) + 1/4(V)
+  int frameSize = width * height * 3 / 2;
+
+  BufferedData buf;
+  buf.SetLength(frameSize);
+  ASSERT_TRUE(buf.Length() == frameSize);
+
+  SFrameBSInfo info;
+  memset(&info, 0, sizeof(SFrameBSInfo));
+
+  while (in->read(buf.data(), frameSize) == frameSize) {
+    rv = encoder_->EncodeFrame(buf.data(), &info);
+    ASSERT_TRUE(rv != videoFrameTypeInvalid);
+    if (rv != videoFrameTypeSkip && cbk != NULL) {
+      cbk->onEncodeFrame(info);
+    }
+  }
+}
+
+void BaseEncoderTest::EncodeFile(const char* fileName, int width, int height,
+    float frameRate, Callback* cbk) {
+  FileInputStream fileStream;
+  ASSERT_TRUE(fileStream.Open(fileName));
+  EncodeStream(&fileStream, width, height, frameRate, cbk);
+}
--- /dev/null
+++ b/test/BaseEncoderTest.h
@@ -1,0 +1,24 @@
+#ifndef __BASEENCODERTEST_H__
+#define __BASEENCODERTEST_H__
+
+#include "codec_api.h"
+#include "codec_app_def.h"
+#include "utils/InputStream.h"
+
+class BaseEncoderTest {
+ public:
+  struct Callback {
+    virtual void onEncodeFrame(const SFrameBSInfo& frameInfo) = 0;
+  };
+
+  BaseEncoderTest();
+  void SetUp();
+  void TearDown();
+  void EncodeFile(const char* fileName, int width, int height, float frameRate, Callback* cbk);
+  void EncodeStream(InputStream* in, int width, int height, float frameRate, Callback* cbk);
+
+ private:
+  ISVCEncoder* encoder_;
+};
+
+#endif //__BASEENCODERTEST_H__
--- /dev/null
+++ b/test/decode_encode_test.cpp
@@ -1,0 +1,112 @@
+#include <gtest/gtest.h>
+#include "codec_def.h"
+#include "utils/HashFunctions.h"
+#include "utils/BufferedData.h"
+#include "utils/InputStream.h"
+#include "BaseDecoderTest.h"
+#include "BaseEncoderTest.h"
+
+static void UpdateHashFromFrame(const SFrameBSInfo& info, SHA_CTX* ctx) {
+  for (int i = 0; i < info.iLayerNum; ++i) {
+    const SLayerBSInfo& layerInfo = info.sLayerInfo[i];
+    int layerSize = 0;
+    for (int j = 0; j < layerInfo.iNalCount; ++j) {
+      layerSize += layerInfo.iNalLengthInByte[j];
+    }
+    SHA1_Update(ctx, layerInfo.pBsBuf, layerSize);
+  }
+}
+
+static void WritePlaneBuffer(BufferedData* buf, const uint8_t* plane,
+    int width, int height, int stride) {
+  for (int i = 0; i < height; i++) {
+    if (!buf->PushBack(plane, width)) {
+      FAIL() << "unable to allocate memory";
+    }
+    plane += stride;
+  }
+}
+
+struct DecodeEncodeFileParam {
+  const char* fileName;
+  const char* hashStr;
+  int width;
+  int height;
+  float frameRate;
+};
+
+class DecodeEncodeTest : public ::testing::TestWithParam<DecodeEncodeFileParam>,
+    public BaseDecoderTest, public BaseDecoderTest::Callback,
+    public BaseEncoderTest , public BaseEncoderTest::Callback,
+    public InputStream {
+ public:
+  virtual void SetUp() {
+    BaseDecoderTest::SetUp();
+    if (HasFatalFailure()) {
+      return;
+    }
+    BaseEncoderTest::SetUp();
+    if (HasFatalFailure()) {
+      return;
+    }
+    SHA1_Init(&ctx_);
+  }
+
+  virtual void TearDown() {
+    BaseDecoderTest::TearDown();
+    BaseEncoderTest::TearDown();
+  }
+
+  virtual void onDecodeFrame(const Frame& frame) {
+    const Plane& y = frame.y;
+    const Plane& u = frame.u;
+    const Plane& v = frame.v;
+    WritePlaneBuffer(&buf_, y.data, y.width, y.height, y.stride);
+    WritePlaneBuffer(&buf_, u.data, u.width, u.height, u.stride);
+    WritePlaneBuffer(&buf_, v.data, v.width, v.height, v.stride);
+  }
+
+  virtual void onEncodeFrame(const SFrameBSInfo& frameInfo) {
+    UpdateHashFromFrame(frameInfo, &ctx_);
+  }
+
+  virtual int read(void* ptr, size_t len) {
+    while (buf_.Length() < len) {
+      bool hasNext = DecodeNextFrame(this);
+      if (HasFatalFailure()) {
+        return -1;
+      }
+      if (!hasNext) {
+        if (buf_.Length() == 0) {
+          return -1;
+        }
+        break;
+      }
+    }
+    return buf_.PopFront(static_cast<uint8_t*>(ptr), len);
+  }
+
+ protected:
+  SHA_CTX ctx_;
+  BufferedData buf_;
+};
+
+TEST_P(DecodeEncodeTest, CompareOutput) {
+  DecodeEncodeFileParam p = GetParam();
+
+  ASSERT_TRUE(Open(p.fileName));
+  EncodeStream(this, p.width, p.height, p.frameRate, this);
+  unsigned char digest[SHA_DIGEST_LENGTH];
+  SHA1_Final(digest, &ctx_);
+  if (!HasFatalFailure()) {
+    ASSERT_TRUE(CompareHash(digest, p.hashStr));
+  }
+}
+
+static const DecodeEncodeFileParam kFileParamArray[] = {
+  {"res/test_vd_1d.264", "41c672107cfe9e8e8a67b5d08cbd701f6c982ccd", 320, 192, 12.0f},
+  {"res/test_vd_rc.264", "d546ea7c671b42503f8a46ba50bef2a3eaca4c5a", 320, 192, 12.0f},
+};
+
+INSTANTIATE_TEST_CASE_P(DecodeEncodeFile, DecodeEncodeTest,
+    ::testing::ValuesIn(kFileParamArray));
--- a/test/decoder_test.cpp
+++ b/test/decoder_test.cpp
@@ -1,12 +1,6 @@
 #include <gtest/gtest.h>
-#include <stdint.h>
-#include <limits.h>
-#include <fstream>
-
-#include "codec_api.h"
-
-#include "utils/BufferedData.h"
 #include "utils/HashFunctions.h"
+#include "BaseDecoderTest.h"
 
 static void UpdateHashFromPlane(SHA_CTX* ctx, const uint8_t* plane,
     int width, int height, int stride) {
@@ -16,168 +10,55 @@
   }
 }
 
-/**
- * @return frame size (>= 0), or -1 for memory allocation error.
- */
-static int ReadFrame(std::ifstream* file, BufferedData* buf) {
-  // start code of a frame is {0, 0, 0, 1}
-  int zeroCount = 0;
-  char b;
 
-  for (;;) {
-    file->read(&b, 1);
-    if (file->gcount() != 1) {
-      break;
-    }
-    if (!buf->Push(b)) {
-      return -1;
-    }
-
-    if (buf->Length() <= 4) {
-      continue;
-    }
-
-    if (zeroCount < 3) {
-      zeroCount = b != 0 ? 0 : zeroCount + 1;
-    } else {
-      if (b == 1) {
-        if (file->seekg(-4, file->cur).good()) {
-          return buf->Length() - 4;
-        } else {
-          // seeking fails
-          return -1;
-        }
-      } else if (b == 0) {
-        zeroCount = 3;
-      } else {
-        zeroCount = 0;
-      }
-    }
-  }
-  return buf->Length();
-}
-
-/**
- * @return true if a frame is decoded successfully, otherwise false.
- */
-static bool DecodeAndProcess(ISVCDecoder* decoder, const uint8_t* src,
-    int sliceSize, SHA_CTX* ctx) {
-  void* data[3];
-  SBufferInfo bufInfo;
-  memset(data, 0, sizeof(data));
-  memset(&bufInfo, 0, sizeof(SBufferInfo));
-
-  DECODING_STATE rv = decoder->DecodeFrame2(src, sliceSize, data, &bufInfo);
-  if (rv != dsErrorFree) {
-    return false;
-  }
-
-  if (bufInfo.iBufferStatus == 1) {
-    // y plane
-    UpdateHashFromPlane(ctx, static_cast<uint8_t*>(data[0]),
-        bufInfo.UsrData.sSystemBuffer.iWidth,
-        bufInfo.UsrData.sSystemBuffer.iHeight,
-        bufInfo.UsrData.sSystemBuffer.iStride[0]);
-    // u plane
-    UpdateHashFromPlane(ctx, static_cast<uint8_t*>(data[1]),
-        bufInfo.UsrData.sSystemBuffer.iWidth / 2,
-        bufInfo.UsrData.sSystemBuffer.iHeight / 2,
-        bufInfo.UsrData.sSystemBuffer.iStride[1]);
-    // v plane
-    UpdateHashFromPlane(ctx, static_cast<uint8_t*>(data[2]),
-        bufInfo.UsrData.sSystemBuffer.iWidth / 2,
-        bufInfo.UsrData.sSystemBuffer.iHeight / 2,
-        bufInfo.UsrData.sSystemBuffer.iStride[1]);
-  }
-  return true;
-}
-
-static void CompareFileToHash(ISVCDecoder* decoder,
-    const char* fileName, const char* hashStr) {
-  std::ifstream file(fileName, std::ios::in | std::ios::binary);
-  ASSERT_TRUE(file.is_open());
-
-  unsigned char digest[SHA_DIGEST_LENGTH];
-  SHA_CTX ctx;
-  SHA1_Init(&ctx);
-
-  BufferedData buf;
-  int sliceSize;
-
-  while ((sliceSize = ReadFrame(&file, &buf)) > 0) {
-    if (DecodeAndProcess(decoder, buf.data(), sliceSize, &ctx)) {
-      buf.Clear();
-    } else {
-      SHA1_Final(digest, &ctx);
-      FAIL() << "unable to decode frame";
-    }
-  }
-
-  if (sliceSize < 0) {
-    SHA1_Final(digest, &ctx);
-    FAIL() << "unable to allocate memory";
-  }
-
-  int32_t iEndOfStreamFlag = 1;
-  decoder->SetOption(DECODER_OPTION_END_OF_STREAM, &iEndOfStreamFlag);
-
-  // Get pending last frame
-  if (!DecodeAndProcess(decoder, NULL, 0, &ctx)) {
-    SHA1_Final(digest, &ctx);
-    FAIL() << "unable to decode last frame";
-  }
-
-  SHA1_Final(digest, &ctx);
-  ASSERT_TRUE(CompareHash(digest, hashStr));
-}
-
-class DecoderInitTest : public ::testing::Test {
+class DecoderInitTest : public ::testing::Test, public BaseDecoderTest {
  public:
-  DecoderInitTest() : decoder_(NULL) {}
-
   virtual void SetUp() {
-    long rv = CreateDecoder(&decoder_);
-    ASSERT_EQ(0, rv);
-    ASSERT_TRUE(decoder_ != NULL);
-
-    SDecodingParam decParam;
-    memset(&decParam, 0, sizeof(SDecodingParam));
-    decParam.iOutputColorFormat  = videoFormatI420;
-    decParam.uiTargetDqLayer = UCHAR_MAX;
-    decParam.uiEcActiveFlag  = 1;
-    decParam.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_DEFAULT;
-
-    rv = decoder_->Initialize(&decParam, INIT_TYPE_PARAMETER_BASED);
-    ASSERT_EQ(0, rv);
+    BaseDecoderTest::SetUp();
   }
-
   virtual void TearDown() {
-    if (decoder_ != NULL) {
-      decoder_->Uninitialize();
-      DestroyDecoder(decoder_);
-    }
+    BaseDecoderTest::TearDown();
   }
-
- protected:
-  ISVCDecoder* decoder_;
 };
 
+TEST_F(DecoderInitTest, JustInit) {}
 
-TEST_F(DecoderInitTest, JustInit) {
-}
-
 struct FileParam {
   const char* fileName;
   const char* hashStr;
 };
 
-class DecoderOutputTest : public DecoderInitTest,
-    public ::testing::WithParamInterface<FileParam> {
+class DecoderOutputTest : public ::testing::WithParamInterface<FileParam>,
+    public DecoderInitTest, public BaseDecoderTest::Callback {
+ public:
+  virtual void SetUp() {
+    DecoderInitTest::SetUp();
+    if (HasFatalFailure()) {
+      return;
+    }
+    SHA1_Init(&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:
+  SHA_CTX ctx_;
 };
 
 TEST_P(DecoderOutputTest, CompareOutput) {
   FileParam p = GetParam();
-  CompareFileToHash(decoder_, p.fileName, p.hashStr);
+  DecodeFile(p.fileName, this);
+
+  unsigned char digest[SHA_DIGEST_LENGTH];
+  SHA1_Final(digest, &ctx_);
+  if (!HasFatalFailure()) {
+    ASSERT_TRUE(CompareHash(digest, p.hashStr));
+  }
 }
 
 static const FileParam kFileParamArray[] = {
--- a/test/encoder_test.cpp
+++ b/test/encoder_test.cpp
@@ -1,43 +1,7 @@
 #include <gtest/gtest.h>
-#include <stdint.h>
-#include <fstream>
-
-#include "codec_api.h"
-
-#include "utils/BufferedData.h"
 #include "utils/HashFunctions.h"
+#include "BaseEncoderTest.h"
 
-static int InitWithParam(ISVCEncoder* encoder, int width,
-    int height, float frameRate) {
-  SVCEncodingParam param;
-  memset (&param, 0, sizeof(SVCEncodingParam));
-
-  param.sSpatialLayers[0].iVideoWidth  = width;
-  param.sSpatialLayers[0].iVideoHeight = height;
-  param.sSpatialLayers[0].fFrameRate = frameRate;
-  param.sSpatialLayers[0].iQualityLayerNum = 1;
-  param.sSpatialLayers[0].iSpatialBitrate = 600000;
-
-  SSliceConfig* sliceCfg = &param.sSpatialLayers[0].sSliceCfg;
-  sliceCfg->sSliceArgument.uiSliceNum = 1;
-  sliceCfg->sSliceArgument.uiSliceSizeConstraint = 1500;
-  sliceCfg->sSliceArgument.uiSliceMbNum[0] = 960;
-
-  param.fFrameRate = param.sSpatialLayers[0].fFrameRate;
-  param.iPicWidth = param.sSpatialLayers[0].iVideoWidth;
-  param.iPicHeight = param.sSpatialLayers[0].iVideoHeight;
-  param.iTargetBitrate = 5000000;
-  param.iTemporalLayerNum = 3;
-  param.iSpatialLayerNum = 1;
-  param.bEnableBackgroundDetection = true;
-  param.bEnableLongTermReference = true;
-  param.iLtrMarkPeriod = 30;
-  param.iInputCsp = videoFormatI420;
-  param.bEnableSpsPpsIdAddition = true;
-
-  return encoder->Initialize(&param, INIT_TYPE_PARAMETER_BASED);
-}
-
 static void UpdateHashFromFrame(const SFrameBSInfo& info, SHA_CTX* ctx) {
   for (int i = 0; i < info.iLayerNum; ++i) {
     const SLayerBSInfo& layerInfo = info.sLayerInfo[i];
@@ -49,68 +13,17 @@
   }
 }
 
-static void CompareFileToHash(ISVCEncoder* encoder,
-    const char* fileName, const char* hashStr,
-    int width, int height, float frameRate) {
-  std::ifstream file(fileName, std::ios::in | std::ios::binary);
-  ASSERT_TRUE(file.is_open());
-
-  int rv = InitWithParam(encoder, width, height, frameRate);
-  ASSERT_TRUE(rv == cmResultSuccess);
-
-  // I420: 1(Y) + 1/4(U) + 1/4(V)
-  int frameSize = width * height * 3 / 2;
-
-  BufferedData buf;
-  buf.SetLength(frameSize);
-  ASSERT_TRUE(buf.Length() == frameSize);
-  char* data = reinterpret_cast<char*>(buf.data());
-
-  SFrameBSInfo info;
-  memset(&info, 0, sizeof(SFrameBSInfo));
-
-  unsigned char digest[SHA_DIGEST_LENGTH];
-  SHA_CTX ctx;
-  SHA1_Init(&ctx);
-
-  while (file.read(data, frameSize), file.gcount() == frameSize) {
-    rv = encoder->EncodeFrame(buf.data(), &info);
-    if (rv == videoFrameTypeInvalid) {
-      SHA1_Final(digest, &ctx);
-      FAIL() << "unable to encode frame";
-    }
-    if (rv != videoFrameTypeSkip) {
-      UpdateHashFromFrame(info, &ctx);
-    }
-  }
-
-  SHA1_Final(digest, &ctx);
-  ASSERT_TRUE(CompareHash(digest, hashStr));
-}
-
-class EncoderBaseTest : public ::testing::Test {
+class EncoderInitTest : public ::testing::Test, public BaseEncoderTest {
  public:
-  EncoderBaseTest() : encoder_(NULL) {}
-
   virtual void SetUp() {
-    int rv = CreateSVCEncoder(&encoder_);
-    ASSERT_EQ(0, rv);
-    ASSERT_TRUE(encoder_ != NULL);
+    BaseEncoderTest::SetUp();
   }
-
   virtual void TearDown() {
-    if (encoder_) {
-      encoder_->Uninitialize();
-      DestroySVCEncoder(encoder_);
-    }
+    BaseEncoderTest::TearDown();
   }
-
- protected:
-  ISVCEncoder* encoder_;
 };
 
-TEST_F(EncoderBaseTest, JustInit) {
-}
+TEST_F(EncoderInitTest, JustInit) {}
 
 struct EncodeFileParam {
   const char* fileName;
@@ -120,14 +33,33 @@
   float frameRate;
 };
 
-class EncoderOutputTest : public EncoderBaseTest ,
-    public ::testing::WithParamInterface<EncodeFileParam> {
+class EncoderOutputTest : public ::testing::WithParamInterface<EncodeFileParam>,
+    public EncoderInitTest , public BaseEncoderTest::Callback {
+ public:
+  virtual void SetUp() {
+    EncoderInitTest::SetUp();
+    if (HasFatalFailure()) {
+      return;
+    }
+    SHA1_Init(&ctx_);
+  }
+  virtual void onEncodeFrame(const SFrameBSInfo& frameInfo) {
+    UpdateHashFromFrame(frameInfo, &ctx_);
+  }
+ protected:
+  SHA_CTX ctx_;
 };
 
 
 TEST_P(EncoderOutputTest, CompareOutput) {
   EncodeFileParam p = GetParam();
-  CompareFileToHash(encoder_, p.fileName, p.hashStr, p.width, p.height, p.frameRate);
+  EncodeFile(p.fileName, p.width, p.height, p.frameRate, this);
+
+  unsigned char digest[SHA_DIGEST_LENGTH];
+  SHA1_Final(digest, &ctx_);
+  if (!HasFatalFailure()) {
+    ASSERT_TRUE(CompareHash(digest, p.hashStr));
+  }
 }
 
 static const EncodeFileParam kFileParamArray[] = {
--- a/test/targets.mk
+++ b/test/targets.mk
@@ -1,8 +1,11 @@
 CODEC_UNITTEST_SRCDIR=test
 CODEC_UNITTEST_CPP_SRCS=\
-	$(CODEC_UNITTEST_SRCDIR)/./decoder_test.cpp\
+	$(CODEC_UNITTEST_SRCDIR)/./BaseDecoderTest.cpp\
 	$(CODEC_UNITTEST_SRCDIR)/./encoder_test.cpp\
+	$(CODEC_UNITTEST_SRCDIR)/./decode_encode_test.cpp\
 	$(CODEC_UNITTEST_SRCDIR)/./simple_test.cpp\
+	$(CODEC_UNITTEST_SRCDIR)/./decoder_test.cpp\
+	$(CODEC_UNITTEST_SRCDIR)/./BaseEncoderTest.cpp\
 
 CODEC_UNITTEST_OBJS += $(CODEC_UNITTEST_CPP_SRCS:.cpp=.o)
 OBJS += $(CODEC_UNITTEST_OBJS)
--- a/test/utils/BufferedData.h
+++ b/test/utils/BufferedData.h
@@ -1,6 +1,9 @@
 #ifndef __BUFFEREDDATA_H__
 #define __BUFFEREDDATA_H__
 
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdint.h>
 
 class BufferedData {
  public:
@@ -10,12 +13,29 @@
     free(data_);
   }
 
-  bool Push(uint8_t c) {
+  bool PushBack(uint8_t c) {
     if (!EnsureCapacity(length_ + 1)) {
       return false;
     }
     data_[length_++] = c;
     return true;
+  }
+
+  bool PushBack(const uint8_t* data, size_t len) {
+    if (!EnsureCapacity(length_ + len)) {
+      return false;
+    }
+    memcpy(data_ + length_, data, len);
+    length_ += len;
+    return true;
+  }
+
+  size_t PopFront(uint8_t* ptr, size_t len) {
+    len = std::min(length_, len);
+    memcpy(ptr, data_, len);
+    memmove(data_, data_ + len, length_ - len);
+    SetLength(length_ - len);
+    return len;
   }
 
   void Clear() {
--- /dev/null
+++ b/test/utils/FileInputStream.h
@@ -1,0 +1,24 @@
+#ifndef __FILEINPUTSTREAM_H__
+#define __FILEINPUTSTREAM_H__
+
+#include <fstream>
+#include "InputStream.h"
+
+class FileInputStream : public InputStream {
+ public:
+  bool Open(const char* fileName) {
+    file_.open(fileName, std::ios_base::in | std::ios_base::binary);
+    return file_.is_open();
+  }
+  int read(void* ptr, size_t len) {
+    if (!file_.good()) {
+      return -1;
+    }
+    file_.read(static_cast<char*>(ptr), len);
+    return file_.gcount();
+  }
+ private:
+  std::ifstream file_;
+};
+
+#endif //__FILEINPUTSTREAM_H__
--- a/test/utils/HashFunctions.h
+++ b/test/utils/HashFunctions.h
@@ -1,6 +1,8 @@
 #ifndef __HASHFUNCTIONS_H__
 #define __HASHFUNCTIONS_H__
 
+#include <stdio.h>
+#include <string.h>
 #include <openssl/sha.h>
 
 static bool CompareHash(const unsigned char* digest, const char* hashStr) {
--- /dev/null
+++ b/test/utils/InputStream.h
@@ -1,0 +1,10 @@
+#ifndef __INPUTSTREAM_H__
+#define __INPUTSTREAM_H__
+
+#include <cstddef>
+
+struct InputStream {
+  virtual int read(void* ptr, size_t len) = 0;
+};
+
+#endif //__INPUTSTREAM_H__