1 /* 2 * Copyright (c) 2014 The WebM project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 #include <string> 12 #include <vector> 13 #include "third_party/googletest/src/include/gtest/gtest.h" 14 #include "test/codec_factory.h" 15 #include "test/encode_test_driver.h" 16 #include "test/md5_helper.h" 17 #include "test/util.h" 18 #include "test/y4m_video_source.h" 19 #include "vp9/encoder/vp9_firstpass.h" 20 21 namespace { 22 // FIRSTPASS_STATS struct: 23 // { 24 // 25 double members; 25 // 1 int64_t member; 26 // } 27 // Whenever FIRSTPASS_STATS struct is modified, the following constants need to 28 // be revisited. 29 const int kDbl = 25; 30 const int kInt = 1; 31 const size_t kFirstPassStatsSz = kDbl * sizeof(double) + kInt * sizeof(int64_t); 32 33 class VPxFirstPassEncoderThreadTest 34 : public ::libvpx_test::EncoderTest, 35 public ::libvpx_test::CodecTestWith2Params<libvpx_test::TestMode, int> { 36 protected: 37 VPxFirstPassEncoderThreadTest() 38 : EncoderTest(GET_PARAM(0)), encoder_initialized_(false), tiles_(0), 39 encoding_mode_(GET_PARAM(1)), set_cpu_used_(GET_PARAM(2)) { 40 init_flags_ = VPX_CODEC_USE_PSNR; 41 42 row_mt_mode_ = 1; 43 first_pass_only_ = true; 44 firstpass_stats_.buf = NULL; 45 firstpass_stats_.sz = 0; 46 } 47 virtual ~VPxFirstPassEncoderThreadTest() { free(firstpass_stats_.buf); } 48 49 virtual void SetUp() { 50 InitializeConfig(); 51 SetMode(encoding_mode_); 52 53 cfg_.rc_end_usage = VPX_VBR; 54 cfg_.rc_2pass_vbr_minsection_pct = 5; 55 cfg_.rc_2pass_vbr_maxsection_pct = 2000; 56 cfg_.rc_max_quantizer = 56; 57 cfg_.rc_min_quantizer = 0; 58 } 59 60 virtual void BeginPassHook(unsigned int /*pass*/) { 61 encoder_initialized_ = false; 62 abort_ = false; 63 } 64 65 virtual void EndPassHook() { 66 // For first pass stats test, only run first pass encoder. 67 if (first_pass_only_ && cfg_.g_pass == VPX_RC_FIRST_PASS) 68 abort_ |= first_pass_only_; 69 } 70 71 virtual void PreEncodeFrameHook(::libvpx_test::VideoSource * /*video*/, 72 ::libvpx_test::Encoder *encoder) { 73 if (!encoder_initialized_) { 74 // Encode in 2-pass mode. 75 encoder->Control(VP9E_SET_TILE_COLUMNS, tiles_); 76 encoder->Control(VP8E_SET_CPUUSED, set_cpu_used_); 77 encoder->Control(VP8E_SET_ENABLEAUTOALTREF, 1); 78 encoder->Control(VP8E_SET_ARNR_MAXFRAMES, 7); 79 encoder->Control(VP8E_SET_ARNR_STRENGTH, 5); 80 encoder->Control(VP8E_SET_ARNR_TYPE, 3); 81 encoder->Control(VP9E_SET_FRAME_PARALLEL_DECODING, 0); 82 83 if (encoding_mode_ == ::libvpx_test::kTwoPassGood) 84 encoder->Control(VP9E_SET_ROW_MT, row_mt_mode_); 85 86 encoder_initialized_ = true; 87 } 88 } 89 90 virtual void StatsPktHook(const vpx_codec_cx_pkt_t *pkt) { 91 const uint8_t *const pkt_buf = 92 reinterpret_cast<uint8_t *>(pkt->data.twopass_stats.buf); 93 const size_t pkt_size = pkt->data.twopass_stats.sz; 94 95 // First pass stats size equals sizeof(FIRSTPASS_STATS) 96 EXPECT_EQ(pkt_size, kFirstPassStatsSz) 97 << "Error: First pass stats size doesn't equal kFirstPassStatsSz"; 98 99 firstpass_stats_.buf = 100 realloc(firstpass_stats_.buf, firstpass_stats_.sz + pkt_size); 101 memcpy((uint8_t *)firstpass_stats_.buf + firstpass_stats_.sz, pkt_buf, 102 pkt_size); 103 firstpass_stats_.sz += pkt_size; 104 } 105 106 bool encoder_initialized_; 107 int tiles_; 108 ::libvpx_test::TestMode encoding_mode_; 109 int set_cpu_used_; 110 int row_mt_mode_; 111 bool first_pass_only_; 112 vpx_fixed_buf_t firstpass_stats_; 113 }; 114 115 static void compare_fp_stats(vpx_fixed_buf_t *fp_stats, double factor) { 116 // fp_stats consists of 2 set of first pass encoding stats. These 2 set of 117 // stats are compared to check if the stats match or at least are very close. 118 FIRSTPASS_STATS *stats1 = reinterpret_cast<FIRSTPASS_STATS *>(fp_stats->buf); 119 int nframes_ = (int)(fp_stats->sz / sizeof(FIRSTPASS_STATS)); 120 FIRSTPASS_STATS *stats2 = stats1 + nframes_ / 2; 121 int i, j; 122 123 // The total stats are also output and included in the first pass stats. Here 124 // ignore that in the comparison. 125 for (i = 0; i < (nframes_ / 2 - 1); ++i) { 126 const double *frame_stats1 = reinterpret_cast<double *>(stats1); 127 const double *frame_stats2 = reinterpret_cast<double *>(stats2); 128 129 for (j = 0; j < kDbl; ++j) { 130 ASSERT_LE(fabs(*frame_stats1 - *frame_stats2), 131 fabs(*frame_stats1) / factor) 132 << "First failure @ frame #" << i << " stat #" << j << " (" 133 << *frame_stats1 << " vs. " << *frame_stats2 << ")"; 134 frame_stats1++; 135 frame_stats2++; 136 } 137 138 stats1++; 139 stats2++; 140 } 141 142 // Reset firstpass_stats_ to 0. 143 memset((uint8_t *)fp_stats->buf, 0, fp_stats->sz); 144 fp_stats->sz = 0; 145 } 146 147 static void compare_fp_stats_md5(vpx_fixed_buf_t *fp_stats) { 148 // fp_stats consists of 2 set of first pass encoding stats. These 2 set of 149 // stats are compared to check if the stats match. 150 uint8_t *stats1 = reinterpret_cast<uint8_t *>(fp_stats->buf); 151 uint8_t *stats2 = stats1 + fp_stats->sz / 2; 152 ::libvpx_test::MD5 md5_row_mt_0, md5_row_mt_1; 153 154 md5_row_mt_0.Add(stats1, fp_stats->sz / 2); 155 const char *md5_row_mt_0_str = md5_row_mt_0.Get(); 156 157 md5_row_mt_1.Add(stats2, fp_stats->sz / 2); 158 const char *md5_row_mt_1_str = md5_row_mt_1.Get(); 159 160 // Check md5 match. 161 ASSERT_STREQ(md5_row_mt_0_str, md5_row_mt_1_str) 162 << "MD5 checksums don't match"; 163 164 // Reset firstpass_stats_ to 0. 165 memset((uint8_t *)fp_stats->buf, 0, fp_stats->sz); 166 fp_stats->sz = 0; 167 } 168 169 TEST_P(VPxFirstPassEncoderThreadTest, FirstPassStatsTest) { 170 ::libvpx_test::Y4mVideoSource video("niklas_1280_720_30.y4m", 0, 60); 171 172 first_pass_only_ = true; 173 cfg_.rc_target_bitrate = 1000; 174 175 // Test row_mt_mode: 0 vs 1 at single thread case(threads = 1, tiles_ = 0) 176 tiles_ = 0; 177 cfg_.g_threads = 1; 178 179 row_mt_mode_ = 0; 180 init_flags_ = VPX_CODEC_USE_PSNR; 181 ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); 182 183 row_mt_mode_ = 1; 184 ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); 185 186 // Compare to check if using or not using row-mt generates close stats. 187 ASSERT_NO_FATAL_FAILURE(compare_fp_stats(&firstpass_stats_, 1000.0)); 188 189 // Test single thread vs multiple threads 190 row_mt_mode_ = 1; 191 tiles_ = 0; 192 193 cfg_.g_threads = 1; 194 init_flags_ = VPX_CODEC_USE_PSNR; 195 ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); 196 197 cfg_.g_threads = 4; 198 ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); 199 200 // Compare to check if single-thread and multi-thread stats are close enough. 201 ASSERT_NO_FATAL_FAILURE(compare_fp_stats(&firstpass_stats_, 1000.0)); 202 203 // Bit exact test in row_mt mode. 204 // When row_mt_mode_=1 and using >1 threads, the encoder generates bit exact 205 // result. 206 row_mt_mode_ = 1; 207 tiles_ = 2; 208 209 cfg_.g_threads = 2; 210 init_flags_ = VPX_CODEC_USE_PSNR; 211 ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); 212 213 cfg_.g_threads = 8; 214 ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); 215 216 // Compare to check if stats match with row-mt=0/1. 217 compare_fp_stats_md5(&firstpass_stats_); 218 } 219 220 class VPxEncoderThreadTest 221 : public ::libvpx_test::EncoderTest, 222 public ::libvpx_test::CodecTestWith4Params<libvpx_test::TestMode, int, 223 int, int> { 224 protected: 225 VPxEncoderThreadTest() 226 : EncoderTest(GET_PARAM(0)), encoder_initialized_(false), 227 tiles_(GET_PARAM(3)), threads_(GET_PARAM(4)), 228 encoding_mode_(GET_PARAM(1)), set_cpu_used_(GET_PARAM(2)) { 229 init_flags_ = VPX_CODEC_USE_PSNR; 230 md5_.clear(); 231 row_mt_mode_ = 1; 232 psnr_ = 0.0; 233 nframes_ = 0; 234 } 235 virtual ~VPxEncoderThreadTest() {} 236 237 virtual void SetUp() { 238 InitializeConfig(); 239 SetMode(encoding_mode_); 240 241 if (encoding_mode_ != ::libvpx_test::kRealTime) { 242 cfg_.rc_end_usage = VPX_VBR; 243 cfg_.rc_2pass_vbr_minsection_pct = 5; 244 cfg_.rc_2pass_vbr_maxsection_pct = 2000; 245 } else { 246 cfg_.g_lag_in_frames = 0; 247 cfg_.rc_end_usage = VPX_CBR; 248 cfg_.g_error_resilient = 1; 249 } 250 cfg_.rc_max_quantizer = 56; 251 cfg_.rc_min_quantizer = 0; 252 } 253 254 virtual void BeginPassHook(unsigned int /*pass*/) { 255 encoder_initialized_ = false; 256 psnr_ = 0.0; 257 nframes_ = 0; 258 } 259 260 virtual void PreEncodeFrameHook(::libvpx_test::VideoSource * /*video*/, 261 ::libvpx_test::Encoder *encoder) { 262 if (!encoder_initialized_) { 263 // Encode 4 column tiles. 264 encoder->Control(VP9E_SET_TILE_COLUMNS, tiles_); 265 encoder->Control(VP8E_SET_CPUUSED, set_cpu_used_); 266 if (encoding_mode_ != ::libvpx_test::kRealTime) { 267 encoder->Control(VP8E_SET_ENABLEAUTOALTREF, 1); 268 encoder->Control(VP8E_SET_ARNR_MAXFRAMES, 7); 269 encoder->Control(VP8E_SET_ARNR_STRENGTH, 5); 270 encoder->Control(VP8E_SET_ARNR_TYPE, 3); 271 encoder->Control(VP9E_SET_FRAME_PARALLEL_DECODING, 0); 272 } else { 273 encoder->Control(VP8E_SET_ENABLEAUTOALTREF, 0); 274 encoder->Control(VP9E_SET_AQ_MODE, 3); 275 } 276 encoder->Control(VP9E_SET_ROW_MT, row_mt_mode_); 277 278 encoder_initialized_ = true; 279 } 280 } 281 282 virtual void PSNRPktHook(const vpx_codec_cx_pkt_t *pkt) { 283 psnr_ += pkt->data.psnr.psnr[0]; 284 nframes_++; 285 } 286 287 virtual void DecompressedFrameHook(const vpx_image_t &img, 288 vpx_codec_pts_t /*pts*/) { 289 ::libvpx_test::MD5 md5_res; 290 md5_res.Add(&img); 291 md5_.push_back(md5_res.Get()); 292 } 293 294 virtual bool HandleDecodeResult(const vpx_codec_err_t res, 295 const libvpx_test::VideoSource & /*video*/, 296 libvpx_test::Decoder * /*decoder*/) { 297 if (res != VPX_CODEC_OK) { 298 EXPECT_EQ(VPX_CODEC_OK, res); 299 return false; 300 } 301 302 return true; 303 } 304 305 double GetAveragePsnr() const { return nframes_ ? (psnr_ / nframes_) : 0.0; } 306 307 bool encoder_initialized_; 308 int tiles_; 309 int threads_; 310 ::libvpx_test::TestMode encoding_mode_; 311 int set_cpu_used_; 312 int row_mt_mode_; 313 double psnr_; 314 unsigned int nframes_; 315 std::vector<std::string> md5_; 316 }; 317 318 TEST_P(VPxEncoderThreadTest, EncoderResultTest) { 319 ::libvpx_test::Y4mVideoSource video("niklas_1280_720_30.y4m", 15, 20); 320 cfg_.rc_target_bitrate = 1000; 321 322 // Part 1: Bit exact test for row_mt_mode_ = 0. 323 // This part keeps original unit tests done before row-mt code is checked in. 324 row_mt_mode_ = 0; 325 326 // Encode using single thread. 327 cfg_.g_threads = 1; 328 init_flags_ = VPX_CODEC_USE_PSNR; 329 ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); 330 const std::vector<std::string> single_thr_md5 = md5_; 331 md5_.clear(); 332 333 // Encode using multiple threads. 334 cfg_.g_threads = threads_; 335 ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); 336 const std::vector<std::string> multi_thr_md5 = md5_; 337 md5_.clear(); 338 339 // Compare to check if two vectors are equal. 340 ASSERT_EQ(single_thr_md5, multi_thr_md5); 341 342 // Part 2: row_mt_mode_ = 0 vs row_mt_mode_ = 1 single thread bit exact test. 343 row_mt_mode_ = 1; 344 345 // Encode using single thread 346 cfg_.g_threads = 1; 347 init_flags_ = VPX_CODEC_USE_PSNR; 348 ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); 349 std::vector<std::string> row_mt_single_thr_md5 = md5_; 350 md5_.clear(); 351 352 ASSERT_EQ(single_thr_md5, row_mt_single_thr_md5); 353 354 // Part 3: Bit exact test with row-mt on 355 // When row_mt_mode_=1 and using >1 threads, the encoder generates bit exact 356 // result. 357 row_mt_mode_ = 1; 358 row_mt_single_thr_md5.clear(); 359 360 // Encode using 2 threads. 361 cfg_.g_threads = 2; 362 init_flags_ = VPX_CODEC_USE_PSNR; 363 ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); 364 row_mt_single_thr_md5 = md5_; 365 md5_.clear(); 366 367 // Encode using multiple threads. 368 cfg_.g_threads = threads_; 369 ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); 370 const std::vector<std::string> row_mt_multi_thr_md5 = md5_; 371 md5_.clear(); 372 373 // Compare to check if two vectors are equal. 374 ASSERT_EQ(row_mt_single_thr_md5, row_mt_multi_thr_md5); 375 376 // Part 4: PSNR test with bit_match_mode_ = 0 377 row_mt_mode_ = 1; 378 379 // Encode using single thread. 380 cfg_.g_threads = 1; 381 init_flags_ = VPX_CODEC_USE_PSNR; 382 ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); 383 const double single_thr_psnr = GetAveragePsnr(); 384 385 // Encode using multiple threads. 386 cfg_.g_threads = threads_; 387 ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); 388 const double multi_thr_psnr = GetAveragePsnr(); 389 390 EXPECT_NEAR(single_thr_psnr, multi_thr_psnr, 0.1); 391 } 392 393 INSTANTIATE_TEST_CASE_P( 394 VP9, VPxFirstPassEncoderThreadTest, 395 ::testing::Combine( 396 ::testing::Values( 397 static_cast<const libvpx_test::CodecFactory *>(&libvpx_test::kVP9)), 398 ::testing::Values(::libvpx_test::kTwoPassGood), 399 ::testing::Range(0, 4))); // cpu_used 400 401 // Split this into two instantiations so that we can distinguish 402 // between very slow runs ( ie cpu_speed 0 ) vs ones that can be 403 // run nightly by adding Large to the title. 404 INSTANTIATE_TEST_CASE_P( 405 VP9, VPxEncoderThreadTest, 406 ::testing::Combine( 407 ::testing::Values( 408 static_cast<const libvpx_test::CodecFactory *>(&libvpx_test::kVP9)), 409 ::testing::Values(::libvpx_test::kTwoPassGood, 410 ::libvpx_test::kOnePassGood, 411 ::libvpx_test::kRealTime), 412 ::testing::Range(3, 9), // cpu_used 413 ::testing::Range(0, 3), // tile_columns 414 ::testing::Range(2, 5))); // threads 415 416 INSTANTIATE_TEST_CASE_P( 417 VP9Large, VPxEncoderThreadTest, 418 ::testing::Combine( 419 ::testing::Values( 420 static_cast<const libvpx_test::CodecFactory *>(&libvpx_test::kVP9)), 421 ::testing::Values(::libvpx_test::kTwoPassGood, 422 ::libvpx_test::kOnePassGood, 423 ::libvpx_test::kRealTime), 424 ::testing::Range(0, 3), // cpu_used 425 ::testing::Range(0, 3), // tile_columns 426 ::testing::Range(2, 5))); // threads 427 428 } // namespace 429