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