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