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
11 #include <memory>
12 #include <string>
13
14 #include "gtest/gtest.h"
15
16 #include "./vpx_config.h"
17 #include "test/codec_factory.h"
18 #include "test/decode_test_driver.h"
19 #include "test/encode_test_driver.h"
20 #include "test/register_state_check.h"
21 #include "test/video_source.h"
22
23 namespace libvpx_test {
InitEncoder(VideoSource * video)24 void Encoder::InitEncoder(VideoSource *video) {
25 vpx_codec_err_t res;
26 const vpx_image_t *img = video->img();
27
28 if (video->img() && !encoder_.priv) {
29 cfg_.g_w = img->d_w;
30 cfg_.g_h = img->d_h;
31 cfg_.g_timebase = video->timebase();
32 cfg_.rc_twopass_stats_in = stats_->buf();
33
34 res = vpx_codec_enc_init(&encoder_, CodecInterface(), &cfg_, init_flags_);
35 ASSERT_EQ(VPX_CODEC_OK, res) << EncoderError();
36
37 #if CONFIG_VP9_ENCODER
38 if (CodecInterface() == &vpx_codec_vp9_cx_algo) {
39 // Default to 1 tile column for VP9.
40 const int log2_tile_columns = 0;
41 res = vpx_codec_control_(&encoder_, VP9E_SET_TILE_COLUMNS,
42 log2_tile_columns);
43 ASSERT_EQ(VPX_CODEC_OK, res) << EncoderError();
44 } else
45 #endif
46 {
47 #if CONFIG_VP8_ENCODER
48 ASSERT_EQ(&vpx_codec_vp8_cx_algo, CodecInterface())
49 << "Unknown Codec Interface";
50 #endif
51 }
52 }
53 }
54
EncodeFrame(VideoSource * video,const vpx_enc_frame_flags_t frame_flags)55 void Encoder::EncodeFrame(VideoSource *video,
56 const vpx_enc_frame_flags_t frame_flags) {
57 if (video->img()) {
58 EncodeFrameInternal(*video, frame_flags);
59 } else {
60 Flush();
61 }
62
63 // Handle twopass stats
64 CxDataIterator iter = GetCxData();
65
66 while (const vpx_codec_cx_pkt_t *pkt = iter.Next()) {
67 if (pkt->kind != VPX_CODEC_STATS_PKT) continue;
68
69 stats_->Append(*pkt);
70 }
71 }
72
EncodeFrameInternal(const VideoSource & video,const vpx_enc_frame_flags_t frame_flags)73 void Encoder::EncodeFrameInternal(const VideoSource &video,
74 const vpx_enc_frame_flags_t frame_flags) {
75 vpx_codec_err_t res;
76 const vpx_image_t *img = video.img();
77
78 // Handle frame resizing
79 if (cfg_.g_w != img->d_w || cfg_.g_h != img->d_h) {
80 cfg_.g_w = img->d_w;
81 cfg_.g_h = img->d_h;
82 res = vpx_codec_enc_config_set(&encoder_, &cfg_);
83 ASSERT_EQ(VPX_CODEC_OK, res) << EncoderError();
84 }
85
86 // Encode the frame
87 API_REGISTER_STATE_CHECK(res = vpx_codec_encode(&encoder_, img, video.pts(),
88 video.duration(), frame_flags,
89 deadline_));
90 ASSERT_EQ(VPX_CODEC_OK, res) << EncoderError();
91 }
92
Flush()93 void Encoder::Flush() {
94 const vpx_codec_err_t res =
95 vpx_codec_encode(&encoder_, nullptr, 0, 0, 0, deadline_);
96 if (!encoder_.priv)
97 ASSERT_EQ(VPX_CODEC_ERROR, res) << EncoderError();
98 else
99 ASSERT_EQ(VPX_CODEC_OK, res) << EncoderError();
100 }
101
InitializeConfig()102 void EncoderTest::InitializeConfig() {
103 const vpx_codec_err_t res = codec_->DefaultEncoderConfig(&cfg_, 0);
104 dec_cfg_ = vpx_codec_dec_cfg_t();
105 ASSERT_EQ(VPX_CODEC_OK, res);
106 }
107
SetMode(TestMode mode)108 void EncoderTest::SetMode(TestMode mode) {
109 switch (mode) {
110 case kRealTime: deadline_ = VPX_DL_REALTIME; break;
111
112 case kOnePassGood:
113 case kTwoPassGood: deadline_ = VPX_DL_GOOD_QUALITY; break;
114
115 case kOnePassBest:
116 case kTwoPassBest: deadline_ = VPX_DL_BEST_QUALITY; break;
117
118 default: ASSERT_TRUE(false) << "Unexpected mode " << mode;
119 }
120
121 if (mode == kTwoPassGood || mode == kTwoPassBest) {
122 passes_ = 2;
123 } else {
124 passes_ = 1;
125 }
126 }
127 // The function should return "true" most of the time, therefore no early
128 // break-out is implemented within the match checking process.
compare_img(const vpx_image_t * img1,const vpx_image_t * img2)129 static bool compare_img(const vpx_image_t *img1, const vpx_image_t *img2) {
130 bool match = (img1->fmt == img2->fmt) && (img1->cs == img2->cs) &&
131 (img1->d_w == img2->d_w) && (img1->d_h == img2->d_h);
132
133 if (!match) return false;
134
135 const unsigned int width_y = img1->d_w;
136 const unsigned int height_y = img1->d_h;
137 unsigned int i;
138 for (i = 0; i < height_y; ++i) {
139 match = (memcmp(img1->planes[VPX_PLANE_Y] + i * img1->stride[VPX_PLANE_Y],
140 img2->planes[VPX_PLANE_Y] + i * img2->stride[VPX_PLANE_Y],
141 width_y) == 0) &&
142 match;
143 }
144 const unsigned int width_uv = (img1->d_w + 1) >> 1;
145 const unsigned int height_uv = (img1->d_h + 1) >> 1;
146 for (i = 0; i < height_uv; ++i) {
147 match = (memcmp(img1->planes[VPX_PLANE_U] + i * img1->stride[VPX_PLANE_U],
148 img2->planes[VPX_PLANE_U] + i * img2->stride[VPX_PLANE_U],
149 width_uv) == 0) &&
150 match;
151 }
152 for (i = 0; i < height_uv; ++i) {
153 match = (memcmp(img1->planes[VPX_PLANE_V] + i * img1->stride[VPX_PLANE_V],
154 img2->planes[VPX_PLANE_V] + i * img2->stride[VPX_PLANE_V],
155 width_uv) == 0) &&
156 match;
157 }
158 return match;
159 }
160
MismatchHook(const vpx_image_t *,const vpx_image_t *)161 void EncoderTest::MismatchHook(const vpx_image_t * /*img1*/,
162 const vpx_image_t * /*img2*/) {
163 ASSERT_TRUE(0) << "Encode/Decode mismatch found";
164 }
165
RunLoop(VideoSource * video)166 void EncoderTest::RunLoop(VideoSource *video) {
167 vpx_codec_dec_cfg_t dec_cfg = vpx_codec_dec_cfg_t();
168
169 stats_.Reset();
170
171 ASSERT_TRUE(passes_ == 1 || passes_ == 2);
172 for (unsigned int pass = 0; pass < passes_; pass++) {
173 vpx_codec_pts_t last_pts = 0;
174
175 if (passes_ == 1) {
176 cfg_.g_pass = VPX_RC_ONE_PASS;
177 } else if (pass == 0) {
178 cfg_.g_pass = VPX_RC_FIRST_PASS;
179 } else {
180 cfg_.g_pass = VPX_RC_LAST_PASS;
181 }
182
183 BeginPassHook(pass);
184 std::unique_ptr<Encoder> encoder(
185 codec_->CreateEncoder(cfg_, deadline_, init_flags_, &stats_));
186 ASSERT_NE(encoder.get(), nullptr);
187
188 ASSERT_NO_FATAL_FAILURE(video->Begin());
189 encoder->InitEncoder(video);
190 ASSERT_FALSE(::testing::Test::HasFatalFailure());
191
192 unsigned long dec_init_flags = 0; // NOLINT
193 // Use fragment decoder if encoder outputs partitions.
194 // NOTE: fragment decoder and partition encoder are only supported by VP8.
195 if (init_flags_ & VPX_CODEC_USE_OUTPUT_PARTITION) {
196 dec_init_flags |= VPX_CODEC_USE_INPUT_FRAGMENTS;
197 }
198 std::unique_ptr<Decoder> decoder(
199 codec_->CreateDecoder(dec_cfg, dec_init_flags));
200 bool again;
201 for (again = true; again; video->Next()) {
202 again = (video->img() != nullptr);
203
204 PreEncodeFrameHook(video);
205 PreEncodeFrameHook(video, encoder.get());
206 encoder->EncodeFrame(video, frame_flags_);
207
208 PostEncodeFrameHook(encoder.get());
209
210 CxDataIterator iter = encoder->GetCxData();
211
212 bool has_cxdata = false;
213 bool has_dxdata = false;
214 while (const vpx_codec_cx_pkt_t *pkt = iter.Next()) {
215 pkt = MutateEncoderOutputHook(pkt);
216 again = true;
217 switch (pkt->kind) {
218 case VPX_CODEC_CX_FRAME_PKT:
219 has_cxdata = true;
220 if (decoder != nullptr && DoDecode()) {
221 PreDecodeFrameHook(video, decoder.get());
222 vpx_codec_err_t res_dec = decoder->DecodeFrame(
223 (const uint8_t *)pkt->data.frame.buf, pkt->data.frame.sz);
224
225 if (!HandleDecodeResult(res_dec, *video, decoder.get())) break;
226
227 has_dxdata = true;
228 }
229 ASSERT_GE(pkt->data.frame.pts, last_pts);
230 last_pts = pkt->data.frame.pts;
231 FramePktHook(pkt);
232 break;
233
234 case VPX_CODEC_PSNR_PKT: PSNRPktHook(pkt); break;
235
236 case VPX_CODEC_STATS_PKT: StatsPktHook(pkt); break;
237
238 default: break;
239 }
240 }
241
242 // Flush the decoder when there are no more fragments.
243 if ((init_flags_ & VPX_CODEC_USE_OUTPUT_PARTITION) && has_dxdata) {
244 const vpx_codec_err_t res_dec = decoder->DecodeFrame(nullptr, 0);
245 if (!HandleDecodeResult(res_dec, *video, decoder.get())) break;
246 }
247
248 if (has_dxdata && has_cxdata) {
249 const vpx_image_t *img_enc = encoder->GetPreviewFrame();
250 DxDataIterator dec_iter = decoder->GetDxData();
251 const vpx_image_t *img_dec = dec_iter.Next();
252 if (img_enc && img_dec) {
253 const bool res = compare_img(img_enc, img_dec);
254 if (!res) { // Mismatch
255 MismatchHook(img_enc, img_dec);
256 }
257 }
258 if (img_dec) DecompressedFrameHook(*img_dec, video->pts());
259 }
260 if (!Continue()) break;
261 }
262
263 EndPassHook();
264
265 if (!Continue()) break;
266 }
267 }
268
269 } // namespace libvpx_test
270