xref: /aosp_15_r20/external/libvpx/test/keyframe_test.cc (revision fb1b10ab9aebc7c7068eedab379b749d7e3900be)
1 /*
2  *  Copyright (c) 2012 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 #include <climits>
11 #include <cstring>
12 #include <vector>
13 #include "gtest/gtest.h"
14 #include "test/codec_factory.h"
15 #include "test/encode_test_driver.h"
16 #include "test/i420_video_source.h"
17 #include "test/util.h"
18 #include "./vpx_config.h"
19 #include "vpx/vp8cx.h"
20 #include "vpx/vpx_codec.h"
21 #include "vpx/vpx_encoder.h"
22 #include "vpx/vpx_image.h"
23 
24 namespace {
25 
26 class KeyframeTest
27     : public ::libvpx_test::EncoderTest,
28       public ::libvpx_test::CodecTestWithParam<libvpx_test::TestMode> {
29  protected:
KeyframeTest()30   KeyframeTest() : EncoderTest(GET_PARAM(0)) {}
31   ~KeyframeTest() override = default;
32 
SetUp()33   void SetUp() override {
34     InitializeConfig();
35     SetMode(GET_PARAM(1));
36     kf_count_ = 0;
37     kf_count_max_ = INT_MAX;
38     kf_do_force_kf_ = false;
39     set_cpu_used_ = 0;
40   }
41 
PreEncodeFrameHook(::libvpx_test::VideoSource * video,::libvpx_test::Encoder * encoder)42   void PreEncodeFrameHook(::libvpx_test::VideoSource *video,
43                           ::libvpx_test::Encoder *encoder) override {
44     if (kf_do_force_kf_) {
45       frame_flags_ = (video->frame() % 3) ? 0 : VPX_EFLAG_FORCE_KF;
46     }
47     if (set_cpu_used_ && video->frame() == 0) {
48       encoder->Control(VP8E_SET_CPUUSED, set_cpu_used_);
49     }
50   }
51 
FramePktHook(const vpx_codec_cx_pkt_t * pkt)52   void FramePktHook(const vpx_codec_cx_pkt_t *pkt) override {
53     if (pkt->data.frame.flags & VPX_FRAME_IS_KEY) {
54       kf_pts_list_.push_back(pkt->data.frame.pts);
55       kf_count_++;
56       abort_ |= kf_count_ > kf_count_max_;
57     }
58   }
59 
60   bool kf_do_force_kf_;
61   int kf_count_;
62   int kf_count_max_;
63   std::vector<vpx_codec_pts_t> kf_pts_list_;
64   int set_cpu_used_;
65 };
66 
TEST_P(KeyframeTest,TestRandomVideoSource)67 TEST_P(KeyframeTest, TestRandomVideoSource) {
68   // Validate that encoding the RandomVideoSource produces multiple keyframes.
69   // This validates the results of the TestDisableKeyframes test.
70   kf_count_max_ = 2;  // early exit successful tests.
71 
72   ::libvpx_test::RandomVideoSource video;
73   ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
74 
75   // In realtime mode - auto placed keyframes are exceedingly rare,  don't
76   // bother with this check   if(GetParam() > 0)
77   if (GET_PARAM(1) > 0) {
78     EXPECT_GT(kf_count_, 1);
79   }
80 }
81 
TEST_P(KeyframeTest,TestDisableKeyframes)82 TEST_P(KeyframeTest, TestDisableKeyframes) {
83   cfg_.kf_mode = VPX_KF_DISABLED;
84   kf_count_max_ = 1;  // early exit failed tests.
85 
86   ::libvpx_test::RandomVideoSource video;
87   ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
88 
89   EXPECT_EQ(1, kf_count_);
90 }
91 
TEST_P(KeyframeTest,TestForceKeyframe)92 TEST_P(KeyframeTest, TestForceKeyframe) {
93   cfg_.kf_mode = VPX_KF_DISABLED;
94   kf_do_force_kf_ = true;
95 
96   ::libvpx_test::DummyVideoSource video;
97   ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
98 
99   // verify that every third frame is a keyframe.
100   for (std::vector<vpx_codec_pts_t>::const_iterator iter = kf_pts_list_.begin();
101        iter != kf_pts_list_.end(); ++iter) {
102     ASSERT_EQ(0, *iter % 3) << "Unexpected keyframe at frame " << *iter;
103   }
104 }
105 
TEST_P(KeyframeTest,TestKeyframeMaxDistance)106 TEST_P(KeyframeTest, TestKeyframeMaxDistance) {
107   cfg_.kf_max_dist = 25;
108 
109   ::libvpx_test::DummyVideoSource video;
110   ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
111 
112   // verify that keyframe interval matches kf_max_dist
113   for (std::vector<vpx_codec_pts_t>::const_iterator iter = kf_pts_list_.begin();
114        iter != kf_pts_list_.end(); ++iter) {
115     ASSERT_EQ(0, *iter % 25) << "Unexpected keyframe at frame " << *iter;
116   }
117 }
118 
TEST_P(KeyframeTest,TestAutoKeyframe)119 TEST_P(KeyframeTest, TestAutoKeyframe) {
120   cfg_.kf_mode = VPX_KF_AUTO;
121   kf_do_force_kf_ = false;
122 
123   // Force a deterministic speed step in Real Time mode, as the faster modes
124   // may not produce a keyframe like we expect. This is necessary when running
125   // on very slow environments (like Valgrind). The step -11 was determined
126   // experimentally as the fastest mode that still throws the keyframe.
127   if (deadline_ == VPX_DL_REALTIME) set_cpu_used_ = -11;
128 
129   // This clip has a cut scene every 30 frames -> Frame 0, 30, 60, 90, 120.
130   // I check only the first 40 frames to make sure there's a keyframe at frame
131   // 0 and 30.
132   ::libvpx_test::I420VideoSource video("hantro_collage_w352h288.yuv", 352, 288,
133                                        30, 1, 0, 40);
134 
135   ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
136 
137   // In realtime mode - auto placed keyframes are exceedingly rare,  don't
138   // bother with this check
139   if (GET_PARAM(1) > 0) {
140     EXPECT_EQ(2u, kf_pts_list_.size()) << " Not the right number of keyframes ";
141   }
142 
143   // Verify that keyframes match the file keyframes in the file.
144   for (std::vector<vpx_codec_pts_t>::const_iterator iter = kf_pts_list_.begin();
145        iter != kf_pts_list_.end(); ++iter) {
146     if (deadline_ == VPX_DL_REALTIME && *iter > 0)
147       EXPECT_EQ(0, (*iter - 1) % 30)
148           << "Unexpected keyframe at frame " << *iter;
149     else
150       EXPECT_EQ(0, *iter % 30) << "Unexpected keyframe at frame " << *iter;
151   }
152 }
153 
154 VP8_INSTANTIATE_TEST_SUITE(KeyframeTest, ALL_TEST_MODES);
155 
IsVP9(vpx_codec_iface_t * iface)156 bool IsVP9(vpx_codec_iface_t *iface) {
157   static const char kVP9Name[] = "WebM Project VP9";
158   return strncmp(kVP9Name, vpx_codec_iface_name(iface), sizeof(kVP9Name) - 1) ==
159          0;
160 }
161 
CreateGrayImage(vpx_img_fmt_t fmt,unsigned int w,unsigned int h)162 vpx_image_t *CreateGrayImage(vpx_img_fmt_t fmt, unsigned int w,
163                              unsigned int h) {
164   vpx_image_t *const image = vpx_img_alloc(nullptr, fmt, w, h, 1);
165   if (!image) return image;
166 
167   for (unsigned int i = 0; i < image->d_h; ++i) {
168     memset(image->planes[0] + i * image->stride[0], 128, image->d_w);
169   }
170   const unsigned int uv_h = (image->d_h + 1) / 2;
171   const unsigned int uv_w = (image->d_w + 1) / 2;
172   for (unsigned int i = 0; i < uv_h; ++i) {
173     memset(image->planes[1] + i * image->stride[1], 128, uv_w);
174     memset(image->planes[2] + i * image->stride[2], 128, uv_w);
175   }
176   return image;
177 }
178 
179 // Tests kf_max_dist in one-pass encoding with zero lag.
TestKeyframeMaximumInterval(vpx_codec_iface_t * iface,vpx_enc_deadline_t deadline,unsigned int kf_max_dist)180 void TestKeyframeMaximumInterval(vpx_codec_iface_t *iface,
181                                  vpx_enc_deadline_t deadline,
182                                  unsigned int kf_max_dist) {
183   vpx_codec_enc_cfg_t cfg;
184   ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, /*usage=*/0),
185             VPX_CODEC_OK);
186   cfg.g_w = 320;
187   cfg.g_h = 240;
188   cfg.g_pass = VPX_RC_ONE_PASS;
189   cfg.g_lag_in_frames = 0;
190   cfg.kf_mode = VPX_KF_AUTO;
191   cfg.kf_min_dist = 0;
192   cfg.kf_max_dist = kf_max_dist;
193 
194   vpx_codec_ctx_t enc;
195   ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);
196 
197   const int speed = IsVP9(iface) ? 9 : -12;
198   ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_CPUUSED, speed), VPX_CODEC_OK);
199 
200   vpx_image_t *image = CreateGrayImage(VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
201   ASSERT_NE(image, nullptr);
202 
203   // Encode frames.
204   const vpx_codec_cx_pkt_t *pkt;
205   const unsigned int num_frames = kf_max_dist == 0 ? 4 : 3 * kf_max_dist + 1;
206   for (unsigned int i = 0; i < num_frames; ++i) {
207     ASSERT_EQ(vpx_codec_encode(&enc, image, i, 1, 0, deadline), VPX_CODEC_OK);
208     vpx_codec_iter_t iter = nullptr;
209     while ((pkt = vpx_codec_get_cx_data(&enc, &iter)) != nullptr) {
210       ASSERT_EQ(pkt->kind, VPX_CODEC_CX_FRAME_PKT);
211       if (kf_max_dist == 0 || i % kf_max_dist == 0) {
212         ASSERT_EQ(pkt->data.frame.flags & VPX_FRAME_IS_KEY, VPX_FRAME_IS_KEY);
213       } else {
214         ASSERT_EQ(pkt->data.frame.flags & VPX_FRAME_IS_KEY, 0u);
215       }
216     }
217   }
218 
219   // Flush the encoder.
220   bool got_data;
221   do {
222     ASSERT_EQ(vpx_codec_encode(&enc, nullptr, 0, 1, 0, deadline), VPX_CODEC_OK);
223     got_data = false;
224     vpx_codec_iter_t iter = nullptr;
225     while ((pkt = vpx_codec_get_cx_data(&enc, &iter)) != nullptr) {
226       ASSERT_EQ(pkt->kind, VPX_CODEC_CX_FRAME_PKT);
227       got_data = true;
228     }
229   } while (got_data);
230 
231   vpx_img_free(image);
232   ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
233 }
234 
TEST(KeyframeIntervalTest,KeyframeMaximumInterval)235 TEST(KeyframeIntervalTest, KeyframeMaximumInterval) {
236   std::vector<vpx_codec_iface_t *> ifaces;
237 #if CONFIG_VP8_ENCODER
238   ifaces.push_back(vpx_codec_vp8_cx());
239 #endif
240 #if CONFIG_VP9_ENCODER
241   ifaces.push_back(vpx_codec_vp9_cx());
242 #endif
243   for (vpx_codec_iface_t *iface : ifaces) {
244     for (vpx_enc_deadline_t deadline :
245          { VPX_DL_REALTIME, VPX_DL_GOOD_QUALITY, VPX_DL_BEST_QUALITY }) {
246       // Test 0 and 1 (both mean all intra), some powers of 2, some multiples
247       // of 10, and some prime numbers.
248       for (unsigned int kf_max_dist :
249            { 0, 1, 2, 3, 4, 7, 10, 13, 16, 20, 23, 29, 32 }) {
250         TestKeyframeMaximumInterval(iface, deadline, kf_max_dist);
251       }
252     }
253   }
254 }
255 
256 }  // namespace
257