xref: /aosp_15_r20/external/libvpx/test/vp9_ethread_test.cc (revision fb1b10ab9aebc7c7068eedab379b749d7e3900be)
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