428 lines · 13.6 KB · C++
RawDownload
/* *  Copyright (c) 2014 The WebM project authors. All Rights Reserved. * *  Use of this source code is governed by a BSD-style license *  that can be found in the LICENSE file in the root of the source *  tree. An additional intellectual property rights grant can be found *  in the file PATENTS.  All contributing project authors may *  be found in the AUTHORS file in the root of the source tree. */ #include <string>#include <vector>#include "third_party/googletest/src/include/gtest/gtest.h"#include "test/codec_factory.h"#include "test/encode_test_driver.h"#include "test/md5_helper.h"#include "test/util.h"#include "test/y4m_video_source.h"#include "vp9/encoder/vp9_firstpass.h" namespace {// FIRSTPASS_STATS struct:// {//   25 double members;//   1 int64_t member;// }// Whenever FIRSTPASS_STATS struct is modified, the following constants need to// be revisited.const int kDbl = 25;const int kInt = 1;const size_t kFirstPassStatsSz = kDbl * sizeof(double) + kInt * sizeof(int64_t); class VPxFirstPassEncoderThreadTest    : public ::libvpx_test::EncoderTest,      public ::libvpx_test::CodecTestWith2Params<libvpx_test::TestMode, int> { protected:  VPxFirstPassEncoderThreadTest()      : EncoderTest(GET_PARAM(0)), encoder_initialized_(false), tiles_(0),        encoding_mode_(GET_PARAM(1)), set_cpu_used_(GET_PARAM(2)) {    init_flags_ = VPX_CODEC_USE_PSNR;     row_mt_mode_ = 1;    first_pass_only_ = true;    firstpass_stats_.buf = nullptr;    firstpass_stats_.sz = 0;  }  virtual ~VPxFirstPassEncoderThreadTest() { free(firstpass_stats_.buf); }   virtual void SetUp() {    InitializeConfig();    SetMode(encoding_mode_);     cfg_.rc_end_usage = VPX_VBR;    cfg_.rc_2pass_vbr_minsection_pct = 5;    cfg_.rc_2pass_vbr_maxsection_pct = 2000;    cfg_.rc_max_quantizer = 56;    cfg_.rc_min_quantizer = 0;  }   virtual void BeginPassHook(unsigned int /*pass*/) {    encoder_initialized_ = false;    abort_ = false;  }   virtual void EndPassHook() {    // For first pass stats test, only run first pass encoder.    if (first_pass_only_ && cfg_.g_pass == VPX_RC_FIRST_PASS)      abort_ |= first_pass_only_;  }   virtual void PreEncodeFrameHook(::libvpx_test::VideoSource * /*video*/,                                  ::libvpx_test::Encoder *encoder) {    if (!encoder_initialized_) {      // Encode in 2-pass mode.      encoder->Control(VP9E_SET_TILE_COLUMNS, tiles_);      encoder->Control(VP8E_SET_CPUUSED, set_cpu_used_);      encoder->Control(VP8E_SET_ENABLEAUTOALTREF, 1);      encoder->Control(VP8E_SET_ARNR_MAXFRAMES, 7);      encoder->Control(VP8E_SET_ARNR_STRENGTH, 5);      encoder->Control(VP8E_SET_ARNR_TYPE, 3);      encoder->Control(VP9E_SET_FRAME_PARALLEL_DECODING, 0);       if (encoding_mode_ == ::libvpx_test::kTwoPassGood)        encoder->Control(VP9E_SET_ROW_MT, row_mt_mode_);       encoder_initialized_ = true;    }  }   virtual void StatsPktHook(const vpx_codec_cx_pkt_t *pkt) {    const uint8_t *const pkt_buf =        reinterpret_cast<uint8_t *>(pkt->data.twopass_stats.buf);    const size_t pkt_size = pkt->data.twopass_stats.sz;     // First pass stats size equals sizeof(FIRSTPASS_STATS)    EXPECT_EQ(pkt_size, kFirstPassStatsSz)        << "Error: First pass stats size doesn't equal kFirstPassStatsSz";     firstpass_stats_.buf =        realloc(firstpass_stats_.buf, firstpass_stats_.sz + pkt_size);    memcpy((uint8_t *)firstpass_stats_.buf + firstpass_stats_.sz, pkt_buf,           pkt_size);    firstpass_stats_.sz += pkt_size;  }   bool encoder_initialized_;  int tiles_;  ::libvpx_test::TestMode encoding_mode_;  int set_cpu_used_;  int row_mt_mode_;  bool first_pass_only_;  vpx_fixed_buf_t firstpass_stats_;}; static void compare_fp_stats(vpx_fixed_buf_t *fp_stats, double factor) {  // fp_stats consists of 2 set of first pass encoding stats. These 2 set of  // stats are compared to check if the stats match or at least are very close.  FIRSTPASS_STATS *stats1 = reinterpret_cast<FIRSTPASS_STATS *>(fp_stats->buf);  int nframes_ = (int)(fp_stats->sz / sizeof(FIRSTPASS_STATS));  FIRSTPASS_STATS *stats2 = stats1 + nframes_ / 2;  int i, j;   // The total stats are also output and included in the first pass stats. Here  // ignore that in the comparison.  for (i = 0; i < (nframes_ / 2 - 1); ++i) {    const double *frame_stats1 = reinterpret_cast<double *>(stats1);    const double *frame_stats2 = reinterpret_cast<double *>(stats2);     for (j = 0; j < kDbl; ++j) {      ASSERT_LE(fabs(*frame_stats1 - *frame_stats2),                fabs(*frame_stats1) / factor)          << "First failure @ frame #" << i << " stat #" << j << " ("          << *frame_stats1 << " vs. " << *frame_stats2 << ")";      frame_stats1++;      frame_stats2++;    }     stats1++;    stats2++;  }   // Reset firstpass_stats_ to 0.  memset((uint8_t *)fp_stats->buf, 0, fp_stats->sz);  fp_stats->sz = 0;} static void compare_fp_stats_md5(vpx_fixed_buf_t *fp_stats) {  // fp_stats consists of 2 set of first pass encoding stats. These 2 set of  // stats are compared to check if the stats match.  uint8_t *stats1 = reinterpret_cast<uint8_t *>(fp_stats->buf);  uint8_t *stats2 = stats1 + fp_stats->sz / 2;  ::libvpx_test::MD5 md5_row_mt_0, md5_row_mt_1;   md5_row_mt_0.Add(stats1, fp_stats->sz / 2);  const char *md5_row_mt_0_str = md5_row_mt_0.Get();   md5_row_mt_1.Add(stats2, fp_stats->sz / 2);  const char *md5_row_mt_1_str = md5_row_mt_1.Get();   // Check md5 match.  ASSERT_STREQ(md5_row_mt_0_str, md5_row_mt_1_str)      << "MD5 checksums don't match";   // Reset firstpass_stats_ to 0.  memset((uint8_t *)fp_stats->buf, 0, fp_stats->sz);  fp_stats->sz = 0;} TEST_P(VPxFirstPassEncoderThreadTest, FirstPassStatsTest) {  ::libvpx_test::Y4mVideoSource video("niklas_1280_720_30.y4m", 0, 60);   first_pass_only_ = true;  cfg_.rc_target_bitrate = 1000;   // Test row_mt_mode: 0 vs 1 at single thread case(threads = 1, tiles_ = 0)  tiles_ = 0;  cfg_.g_threads = 1;   row_mt_mode_ = 0;  init_flags_ = VPX_CODEC_USE_PSNR;  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));   row_mt_mode_ = 1;  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));   // Compare to check if using or not using row-mt generates close stats.  ASSERT_NO_FATAL_FAILURE(compare_fp_stats(&firstpass_stats_, 1000.0));   // Test single thread vs multiple threads  row_mt_mode_ = 1;  tiles_ = 0;   cfg_.g_threads = 1;  init_flags_ = VPX_CODEC_USE_PSNR;  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));   cfg_.g_threads = 4;  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));   // Compare to check if single-thread and multi-thread stats are close enough.  ASSERT_NO_FATAL_FAILURE(compare_fp_stats(&firstpass_stats_, 1000.0));   // Bit exact test in row_mt mode.  // When row_mt_mode_=1 and using >1 threads, the encoder generates bit exact  // result.  row_mt_mode_ = 1;  tiles_ = 2;   cfg_.g_threads = 2;  init_flags_ = VPX_CODEC_USE_PSNR;  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));   cfg_.g_threads = 8;  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));   // Compare to check if stats match with row-mt=0/1.  compare_fp_stats_md5(&firstpass_stats_);} class VPxEncoderThreadTest    : public ::libvpx_test::EncoderTest,      public ::libvpx_test::CodecTestWith4Params<libvpx_test::TestMode, int,                                                 int, int> { protected:  VPxEncoderThreadTest()      : EncoderTest(GET_PARAM(0)), encoder_initialized_(false),        tiles_(GET_PARAM(3)), threads_(GET_PARAM(4)),        encoding_mode_(GET_PARAM(1)), set_cpu_used_(GET_PARAM(2)) {    init_flags_ = VPX_CODEC_USE_PSNR;    md5_.clear();    row_mt_mode_ = 1;    psnr_ = 0.0;    nframes_ = 0;  }  virtual ~VPxEncoderThreadTest() {}   virtual void SetUp() {    InitializeConfig();    SetMode(encoding_mode_);     if (encoding_mode_ != ::libvpx_test::kRealTime) {      cfg_.rc_end_usage = VPX_VBR;      cfg_.rc_2pass_vbr_minsection_pct = 5;      cfg_.rc_2pass_vbr_maxsection_pct = 2000;    } else {      cfg_.g_lag_in_frames = 0;      cfg_.rc_end_usage = VPX_CBR;      cfg_.g_error_resilient = 1;    }    cfg_.rc_max_quantizer = 56;    cfg_.rc_min_quantizer = 0;  }   virtual void BeginPassHook(unsigned int /*pass*/) {    encoder_initialized_ = false;    psnr_ = 0.0;    nframes_ = 0;  }   virtual void PreEncodeFrameHook(::libvpx_test::VideoSource * /*video*/,                                  ::libvpx_test::Encoder *encoder) {    if (!encoder_initialized_) {      // Encode 4 column tiles.      encoder->Control(VP9E_SET_TILE_COLUMNS, tiles_);      encoder->Control(VP8E_SET_CPUUSED, set_cpu_used_);      if (encoding_mode_ != ::libvpx_test::kRealTime) {        encoder->Control(VP8E_SET_ENABLEAUTOALTREF, 1);        encoder->Control(VP8E_SET_ARNR_MAXFRAMES, 7);        encoder->Control(VP8E_SET_ARNR_STRENGTH, 5);        encoder->Control(VP8E_SET_ARNR_TYPE, 3);        encoder->Control(VP9E_SET_FRAME_PARALLEL_DECODING, 0);      } else {        encoder->Control(VP8E_SET_ENABLEAUTOALTREF, 0);        encoder->Control(VP9E_SET_AQ_MODE, 3);      }      encoder->Control(VP9E_SET_ROW_MT, row_mt_mode_);       encoder_initialized_ = true;    }  }   virtual void PSNRPktHook(const vpx_codec_cx_pkt_t *pkt) {    psnr_ += pkt->data.psnr.psnr[0];    nframes_++;  }   virtual void DecompressedFrameHook(const vpx_image_t &img,                                     vpx_codec_pts_t /*pts*/) {    ::libvpx_test::MD5 md5_res;    md5_res.Add(&img);    md5_.push_back(md5_res.Get());  }   virtual bool HandleDecodeResult(const vpx_codec_err_t res,                                  const libvpx_test::VideoSource & /*video*/,                                  libvpx_test::Decoder * /*decoder*/) {    if (res != VPX_CODEC_OK) {      EXPECT_EQ(VPX_CODEC_OK, res);      return false;    }     return true;  }   double GetAveragePsnr() const { return nframes_ ? (psnr_ / nframes_) : 0.0; }   bool encoder_initialized_;  int tiles_;  int threads_;  ::libvpx_test::TestMode encoding_mode_;  int set_cpu_used_;  int row_mt_mode_;  double psnr_;  unsigned int nframes_;  std::vector<std::string> md5_;}; TEST_P(VPxEncoderThreadTest, EncoderResultTest) {  ::libvpx_test::Y4mVideoSource video("niklas_1280_720_30.y4m", 15, 20);  cfg_.rc_target_bitrate = 1000;   // Part 1: Bit exact test for row_mt_mode_ = 0.  // This part keeps original unit tests done before row-mt code is checked in.  row_mt_mode_ = 0;   // Encode using single thread.  cfg_.g_threads = 1;  init_flags_ = VPX_CODEC_USE_PSNR;  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));  const std::vector<std::string> single_thr_md5 = md5_;  md5_.clear();   // Encode using multiple threads.  cfg_.g_threads = threads_;  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));  const std::vector<std::string> multi_thr_md5 = md5_;  md5_.clear();   // Compare to check if two vectors are equal.  ASSERT_EQ(single_thr_md5, multi_thr_md5);   // Part 2: row_mt_mode_ = 0 vs row_mt_mode_ = 1 single thread bit exact test.  row_mt_mode_ = 1;   // Encode using single thread  cfg_.g_threads = 1;  init_flags_ = VPX_CODEC_USE_PSNR;  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));  std::vector<std::string> row_mt_single_thr_md5 = md5_;  md5_.clear();   ASSERT_EQ(single_thr_md5, row_mt_single_thr_md5);   // Part 3: Bit exact test with row-mt on  // When row_mt_mode_=1 and using >1 threads, the encoder generates bit exact  // result.  row_mt_mode_ = 1;  row_mt_single_thr_md5.clear();   // Encode using 2 threads.  cfg_.g_threads = 2;  init_flags_ = VPX_CODEC_USE_PSNR;  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));  row_mt_single_thr_md5 = md5_;  md5_.clear();   // Encode using multiple threads.  cfg_.g_threads = threads_;  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));  const std::vector<std::string> row_mt_multi_thr_md5 = md5_;  md5_.clear();   // Compare to check if two vectors are equal.  ASSERT_EQ(row_mt_single_thr_md5, row_mt_multi_thr_md5);   // Part 4: PSNR test with bit_match_mode_ = 0  row_mt_mode_ = 1;   // Encode using single thread.  cfg_.g_threads = 1;  init_flags_ = VPX_CODEC_USE_PSNR;  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));  const double single_thr_psnr = GetAveragePsnr();   // Encode using multiple threads.  cfg_.g_threads = threads_;  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));  const double multi_thr_psnr = GetAveragePsnr();   EXPECT_NEAR(single_thr_psnr, multi_thr_psnr, 0.2);} INSTANTIATE_TEST_SUITE_P(    VP9, VPxFirstPassEncoderThreadTest,    ::testing::Combine(        ::testing::Values(            static_cast<const libvpx_test::CodecFactory *>(&libvpx_test::kVP9)),        ::testing::Values(::libvpx_test::kTwoPassGood),        ::testing::Range(0, 4)));  // cpu_used // Split this into two instantiations so that we can distinguish// between very slow runs ( ie cpu_speed 0 ) vs ones that can be// run nightly by adding Large to the title.INSTANTIATE_TEST_SUITE_P(    VP9, VPxEncoderThreadTest,    ::testing::Combine(        ::testing::Values(            static_cast<const libvpx_test::CodecFactory *>(&libvpx_test::kVP9)),        ::testing::Values(::libvpx_test::kTwoPassGood,                          ::libvpx_test::kOnePassGood,                          ::libvpx_test::kRealTime),        ::testing::Range(3, 10),   // cpu_used        ::testing::Range(0, 3),    // tile_columns        ::testing::Range(2, 5)));  // threads INSTANTIATE_TEST_SUITE_P(    VP9Large, VPxEncoderThreadTest,    ::testing::Combine(        ::testing::Values(            static_cast<const libvpx_test::CodecFactory *>(&libvpx_test::kVP9)),        ::testing::Values(::libvpx_test::kTwoPassGood,                          ::libvpx_test::kOnePassGood,                          ::libvpx_test::kRealTime),        ::testing::Range(0, 3),    // cpu_used        ::testing::Range(0, 3),    // tile_columns        ::testing::Range(2, 5)));  // threads }  // namespace

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t