| /* |
| * 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 |