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 13 #include "./vpx_config.h" 14 #include "test/codec_factory.h" 15 #include "test/decode_test_driver.h" 16 #include "test/ivf_video_source.h" 17 #include "test/md5_helper.h" 18 #include "test/test_vectors.h" 19 #include "test/util.h" 20 #if CONFIG_WEBM_IO 21 #include "test/webm_video_source.h" 22 #endif 23 24 namespace { 25 26 const int kVideoNameParam = 1; 27 28 struct ExternalFrameBuffer { 29 uint8_t *data; 30 size_t size; 31 int in_use; 32 }; 33 34 // Class to manipulate a list of external frame buffers. 35 class ExternalFrameBufferList { 36 public: 37 ExternalFrameBufferList() : num_buffers_(0), ext_fb_list_(NULL) {} 38 39 virtual ~ExternalFrameBufferList() { 40 for (int i = 0; i < num_buffers_; ++i) { 41 delete[] ext_fb_list_[i].data; 42 } 43 delete[] ext_fb_list_; 44 } 45 46 // Creates the list to hold the external buffers. Returns true on success. 47 bool CreateBufferList(int num_buffers) { 48 if (num_buffers < 0) return false; 49 50 num_buffers_ = num_buffers; 51 ext_fb_list_ = new ExternalFrameBuffer[num_buffers_]; 52 EXPECT_TRUE(ext_fb_list_ != NULL); 53 memset(ext_fb_list_, 0, sizeof(ext_fb_list_[0]) * num_buffers_); 54 return true; 55 } 56 57 // Searches the frame buffer list for a free frame buffer. Makes sure 58 // that the frame buffer is at least |min_size| in bytes. Marks that the 59 // frame buffer is in use by libvpx. Finally sets |fb| to point to the 60 // external frame buffer. Returns < 0 on an error. 61 int GetFreeFrameBuffer(size_t min_size, vpx_codec_frame_buffer_t *fb) { 62 EXPECT_TRUE(fb != NULL); 63 const int idx = FindFreeBufferIndex(); 64 if (idx == num_buffers_) return -1; 65 66 if (ext_fb_list_[idx].size < min_size) { 67 delete[] ext_fb_list_[idx].data; 68 ext_fb_list_[idx].data = new uint8_t[min_size]; 69 memset(ext_fb_list_[idx].data, 0, min_size); 70 ext_fb_list_[idx].size = min_size; 71 } 72 73 SetFrameBuffer(idx, fb); 74 return 0; 75 } 76 77 // Test function that will not allocate any data for the frame buffer. 78 // Returns < 0 on an error. 79 int GetZeroFrameBuffer(size_t min_size, vpx_codec_frame_buffer_t *fb) { 80 EXPECT_TRUE(fb != NULL); 81 const int idx = FindFreeBufferIndex(); 82 if (idx == num_buffers_) return -1; 83 84 if (ext_fb_list_[idx].size < min_size) { 85 delete[] ext_fb_list_[idx].data; 86 ext_fb_list_[idx].data = NULL; 87 ext_fb_list_[idx].size = min_size; 88 } 89 90 SetFrameBuffer(idx, fb); 91 return 0; 92 } 93 94 // Marks the external frame buffer that |fb| is pointing to as free. 95 // Returns < 0 on an error. 96 int ReturnFrameBuffer(vpx_codec_frame_buffer_t *fb) { 97 if (fb == NULL) { 98 EXPECT_TRUE(fb != NULL); 99 return -1; 100 } 101 ExternalFrameBuffer *const ext_fb = 102 reinterpret_cast<ExternalFrameBuffer *>(fb->priv); 103 if (ext_fb == NULL) { 104 EXPECT_TRUE(ext_fb != NULL); 105 return -1; 106 } 107 EXPECT_EQ(1, ext_fb->in_use); 108 ext_fb->in_use = 0; 109 return 0; 110 } 111 112 // Checks that the ximage data is contained within the external frame buffer 113 // private data passed back in the ximage. 114 void CheckXImageFrameBuffer(const vpx_image_t *img) { 115 if (img->fb_priv != NULL) { 116 const struct ExternalFrameBuffer *const ext_fb = 117 reinterpret_cast<ExternalFrameBuffer *>(img->fb_priv); 118 119 ASSERT_TRUE(img->planes[0] >= ext_fb->data && 120 img->planes[0] < (ext_fb->data + ext_fb->size)); 121 } 122 } 123 124 private: 125 // Returns the index of the first free frame buffer. Returns |num_buffers_| 126 // if there are no free frame buffers. 127 int FindFreeBufferIndex() { 128 int i; 129 // Find a free frame buffer. 130 for (i = 0; i < num_buffers_; ++i) { 131 if (!ext_fb_list_[i].in_use) break; 132 } 133 return i; 134 } 135 136 // Sets |fb| to an external frame buffer. idx is the index into the frame 137 // buffer list. 138 void SetFrameBuffer(int idx, vpx_codec_frame_buffer_t *fb) { 139 ASSERT_TRUE(fb != NULL); 140 fb->data = ext_fb_list_[idx].data; 141 fb->size = ext_fb_list_[idx].size; 142 ASSERT_EQ(0, ext_fb_list_[idx].in_use); 143 ext_fb_list_[idx].in_use = 1; 144 fb->priv = &ext_fb_list_[idx]; 145 } 146 147 int num_buffers_; 148 ExternalFrameBuffer *ext_fb_list_; 149 }; 150 151 #if CONFIG_WEBM_IO 152 153 // Callback used by libvpx to request the application to return a frame 154 // buffer of at least |min_size| in bytes. 155 int get_vp9_frame_buffer(void *user_priv, size_t min_size, 156 vpx_codec_frame_buffer_t *fb) { 157 ExternalFrameBufferList *const fb_list = 158 reinterpret_cast<ExternalFrameBufferList *>(user_priv); 159 return fb_list->GetFreeFrameBuffer(min_size, fb); 160 } 161 162 // Callback used by libvpx to tell the application that |fb| is not needed 163 // anymore. 164 int release_vp9_frame_buffer(void *user_priv, vpx_codec_frame_buffer_t *fb) { 165 ExternalFrameBufferList *const fb_list = 166 reinterpret_cast<ExternalFrameBufferList *>(user_priv); 167 return fb_list->ReturnFrameBuffer(fb); 168 } 169 170 // Callback will not allocate data for frame buffer. 171 int get_vp9_zero_frame_buffer(void *user_priv, size_t min_size, 172 vpx_codec_frame_buffer_t *fb) { 173 ExternalFrameBufferList *const fb_list = 174 reinterpret_cast<ExternalFrameBufferList *>(user_priv); 175 return fb_list->GetZeroFrameBuffer(min_size, fb); 176 } 177 178 // Callback will allocate one less byte than |min_size|. 179 int get_vp9_one_less_byte_frame_buffer(void *user_priv, size_t min_size, 180 vpx_codec_frame_buffer_t *fb) { 181 ExternalFrameBufferList *const fb_list = 182 reinterpret_cast<ExternalFrameBufferList *>(user_priv); 183 return fb_list->GetFreeFrameBuffer(min_size - 1, fb); 184 } 185 186 // Callback will not release the external frame buffer. 187 int do_not_release_vp9_frame_buffer(void *user_priv, 188 vpx_codec_frame_buffer_t *fb) { 189 (void)user_priv; 190 (void)fb; 191 return 0; 192 } 193 194 #endif // CONFIG_WEBM_IO 195 196 // Class for testing passing in external frame buffers to libvpx. 197 class ExternalFrameBufferMD5Test 198 : public ::libvpx_test::DecoderTest, 199 public ::libvpx_test::CodecTestWithParam<const char *> { 200 protected: 201 ExternalFrameBufferMD5Test() 202 : DecoderTest(GET_PARAM(::libvpx_test::kCodecFactoryParam)), 203 md5_file_(NULL), num_buffers_(0) {} 204 205 virtual ~ExternalFrameBufferMD5Test() { 206 if (md5_file_ != NULL) fclose(md5_file_); 207 } 208 209 virtual void PreDecodeFrameHook( 210 const libvpx_test::CompressedVideoSource &video, 211 libvpx_test::Decoder *decoder) { 212 if (num_buffers_ > 0 && video.frame_number() == 0) { 213 // Have libvpx use frame buffers we create. 214 ASSERT_TRUE(fb_list_.CreateBufferList(num_buffers_)); 215 ASSERT_EQ(VPX_CODEC_OK, 216 decoder->SetFrameBufferFunctions(GetVP9FrameBuffer, 217 ReleaseVP9FrameBuffer, this)); 218 } 219 } 220 221 void OpenMD5File(const std::string &md5_file_name_) { 222 md5_file_ = libvpx_test::OpenTestDataFile(md5_file_name_); 223 ASSERT_TRUE(md5_file_ != NULL) << "Md5 file open failed. Filename: " 224 << md5_file_name_; 225 } 226 227 virtual void DecompressedFrameHook(const vpx_image_t &img, 228 const unsigned int frame_number) { 229 ASSERT_TRUE(md5_file_ != NULL); 230 char expected_md5[33]; 231 char junk[128]; 232 233 // Read correct md5 checksums. 234 const int res = fscanf(md5_file_, "%s %s", expected_md5, junk); 235 ASSERT_NE(EOF, res) << "Read md5 data failed"; 236 expected_md5[32] = '\0'; 237 238 ::libvpx_test::MD5 md5_res; 239 md5_res.Add(&img); 240 const char *const actual_md5 = md5_res.Get(); 241 242 // Check md5 match. 243 ASSERT_STREQ(expected_md5, actual_md5) 244 << "Md5 checksums don't match: frame number = " << frame_number; 245 } 246 247 // Callback to get a free external frame buffer. Return value < 0 is an 248 // error. 249 static int GetVP9FrameBuffer(void *user_priv, size_t min_size, 250 vpx_codec_frame_buffer_t *fb) { 251 ExternalFrameBufferMD5Test *const md5Test = 252 reinterpret_cast<ExternalFrameBufferMD5Test *>(user_priv); 253 return md5Test->fb_list_.GetFreeFrameBuffer(min_size, fb); 254 } 255 256 // Callback to release an external frame buffer. Return value < 0 is an 257 // error. 258 static int ReleaseVP9FrameBuffer(void *user_priv, 259 vpx_codec_frame_buffer_t *fb) { 260 ExternalFrameBufferMD5Test *const md5Test = 261 reinterpret_cast<ExternalFrameBufferMD5Test *>(user_priv); 262 return md5Test->fb_list_.ReturnFrameBuffer(fb); 263 } 264 265 void set_num_buffers(int num_buffers) { num_buffers_ = num_buffers; } 266 int num_buffers() const { return num_buffers_; } 267 268 private: 269 FILE *md5_file_; 270 int num_buffers_; 271 ExternalFrameBufferList fb_list_; 272 }; 273 274 #if CONFIG_WEBM_IO 275 const char kVP9TestFile[] = "vp90-2-02-size-lf-1920x1080.webm"; 276 277 // Class for testing passing in external frame buffers to libvpx. 278 class ExternalFrameBufferTest : public ::testing::Test { 279 protected: 280 ExternalFrameBufferTest() : video_(NULL), decoder_(NULL), num_buffers_(0) {} 281 282 virtual void SetUp() { 283 video_ = new libvpx_test::WebMVideoSource(kVP9TestFile); 284 ASSERT_TRUE(video_ != NULL); 285 video_->Init(); 286 video_->Begin(); 287 288 vpx_codec_dec_cfg_t cfg = vpx_codec_dec_cfg_t(); 289 decoder_ = new libvpx_test::VP9Decoder(cfg, 0); 290 ASSERT_TRUE(decoder_ != NULL); 291 } 292 293 virtual void TearDown() { 294 delete decoder_; 295 delete video_; 296 } 297 298 // Passes the external frame buffer information to libvpx. 299 vpx_codec_err_t SetFrameBufferFunctions( 300 int num_buffers, vpx_get_frame_buffer_cb_fn_t cb_get, 301 vpx_release_frame_buffer_cb_fn_t cb_release) { 302 if (num_buffers > 0) { 303 num_buffers_ = num_buffers; 304 EXPECT_TRUE(fb_list_.CreateBufferList(num_buffers_)); 305 } 306 307 return decoder_->SetFrameBufferFunctions(cb_get, cb_release, &fb_list_); 308 } 309 310 vpx_codec_err_t DecodeOneFrame() { 311 const vpx_codec_err_t res = 312 decoder_->DecodeFrame(video_->cxdata(), video_->frame_size()); 313 CheckDecodedFrames(); 314 if (res == VPX_CODEC_OK) video_->Next(); 315 return res; 316 } 317 318 vpx_codec_err_t DecodeRemainingFrames() { 319 for (; video_->cxdata() != NULL; video_->Next()) { 320 const vpx_codec_err_t res = 321 decoder_->DecodeFrame(video_->cxdata(), video_->frame_size()); 322 if (res != VPX_CODEC_OK) return res; 323 CheckDecodedFrames(); 324 } 325 return VPX_CODEC_OK; 326 } 327 328 private: 329 void CheckDecodedFrames() { 330 libvpx_test::DxDataIterator dec_iter = decoder_->GetDxData(); 331 const vpx_image_t *img = NULL; 332 333 // Get decompressed data 334 while ((img = dec_iter.Next()) != NULL) { 335 fb_list_.CheckXImageFrameBuffer(img); 336 } 337 } 338 339 libvpx_test::WebMVideoSource *video_; 340 libvpx_test::VP9Decoder *decoder_; 341 int num_buffers_; 342 ExternalFrameBufferList fb_list_; 343 }; 344 #endif // CONFIG_WEBM_IO 345 346 // This test runs through the set of test vectors, and decodes them. 347 // Libvpx will call into the application to allocate a frame buffer when 348 // needed. The md5 checksums are computed for each frame in the video file. 349 // If md5 checksums match the correct md5 data, then the test is passed. 350 // Otherwise, the test failed. 351 TEST_P(ExternalFrameBufferMD5Test, ExtFBMD5Match) { 352 const std::string filename = GET_PARAM(kVideoNameParam); 353 354 // Number of buffers equals #VP9_MAXIMUM_REF_BUFFERS + 355 // #VPX_MAXIMUM_WORK_BUFFERS + four jitter buffers. 356 const int jitter_buffers = 4; 357 const int num_buffers = 358 VP9_MAXIMUM_REF_BUFFERS + VPX_MAXIMUM_WORK_BUFFERS + jitter_buffers; 359 set_num_buffers(num_buffers); 360 361 #if CONFIG_VP8_DECODER 362 // Tell compiler we are not using kVP8TestVectors. 363 (void)libvpx_test::kVP8TestVectors; 364 #endif 365 366 // Open compressed video file. 367 testing::internal::scoped_ptr<libvpx_test::CompressedVideoSource> video; 368 if (filename.substr(filename.length() - 3, 3) == "ivf") { 369 video.reset(new libvpx_test::IVFVideoSource(filename)); 370 } else { 371 #if CONFIG_WEBM_IO 372 video.reset(new libvpx_test::WebMVideoSource(filename)); 373 #else 374 fprintf(stderr, "WebM IO is disabled, skipping test vector %s\n", 375 filename.c_str()); 376 return; 377 #endif 378 } 379 ASSERT_TRUE(video.get() != NULL); 380 video->Init(); 381 382 // Construct md5 file name. 383 const std::string md5_filename = filename + ".md5"; 384 OpenMD5File(md5_filename); 385 386 // Decode frame, and check the md5 matching. 387 ASSERT_NO_FATAL_FAILURE(RunLoop(video.get())); 388 } 389 390 #if CONFIG_WEBM_IO 391 TEST_F(ExternalFrameBufferTest, MinFrameBuffers) { 392 // Minimum number of external frame buffers for VP9 is 393 // #VP9_MAXIMUM_REF_BUFFERS + #VPX_MAXIMUM_WORK_BUFFERS. 394 const int num_buffers = VP9_MAXIMUM_REF_BUFFERS + VPX_MAXIMUM_WORK_BUFFERS; 395 ASSERT_EQ(VPX_CODEC_OK, 396 SetFrameBufferFunctions(num_buffers, get_vp9_frame_buffer, 397 release_vp9_frame_buffer)); 398 ASSERT_EQ(VPX_CODEC_OK, DecodeRemainingFrames()); 399 } 400 401 TEST_F(ExternalFrameBufferTest, EightJitterBuffers) { 402 // Number of buffers equals #VP9_MAXIMUM_REF_BUFFERS + 403 // #VPX_MAXIMUM_WORK_BUFFERS + eight jitter buffers. 404 const int jitter_buffers = 8; 405 const int num_buffers = 406 VP9_MAXIMUM_REF_BUFFERS + VPX_MAXIMUM_WORK_BUFFERS + jitter_buffers; 407 ASSERT_EQ(VPX_CODEC_OK, 408 SetFrameBufferFunctions(num_buffers, get_vp9_frame_buffer, 409 release_vp9_frame_buffer)); 410 ASSERT_EQ(VPX_CODEC_OK, DecodeRemainingFrames()); 411 } 412 413 TEST_F(ExternalFrameBufferTest, NotEnoughBuffers) { 414 // Minimum number of external frame buffers for VP9 is 415 // #VP9_MAXIMUM_REF_BUFFERS + #VPX_MAXIMUM_WORK_BUFFERS. Most files will 416 // only use 5 frame buffers at one time. 417 const int num_buffers = 2; 418 ASSERT_EQ(VPX_CODEC_OK, 419 SetFrameBufferFunctions(num_buffers, get_vp9_frame_buffer, 420 release_vp9_frame_buffer)); 421 ASSERT_EQ(VPX_CODEC_OK, DecodeOneFrame()); 422 ASSERT_EQ(VPX_CODEC_MEM_ERROR, DecodeRemainingFrames()); 423 } 424 425 TEST_F(ExternalFrameBufferTest, NoRelease) { 426 const int num_buffers = VP9_MAXIMUM_REF_BUFFERS + VPX_MAXIMUM_WORK_BUFFERS; 427 ASSERT_EQ(VPX_CODEC_OK, 428 SetFrameBufferFunctions(num_buffers, get_vp9_frame_buffer, 429 do_not_release_vp9_frame_buffer)); 430 ASSERT_EQ(VPX_CODEC_OK, DecodeOneFrame()); 431 ASSERT_EQ(VPX_CODEC_MEM_ERROR, DecodeRemainingFrames()); 432 } 433 434 TEST_F(ExternalFrameBufferTest, NullRealloc) { 435 const int num_buffers = VP9_MAXIMUM_REF_BUFFERS + VPX_MAXIMUM_WORK_BUFFERS; 436 ASSERT_EQ(VPX_CODEC_OK, 437 SetFrameBufferFunctions(num_buffers, get_vp9_zero_frame_buffer, 438 release_vp9_frame_buffer)); 439 ASSERT_EQ(VPX_CODEC_MEM_ERROR, DecodeOneFrame()); 440 } 441 442 TEST_F(ExternalFrameBufferTest, ReallocOneLessByte) { 443 const int num_buffers = VP9_MAXIMUM_REF_BUFFERS + VPX_MAXIMUM_WORK_BUFFERS; 444 ASSERT_EQ(VPX_CODEC_OK, SetFrameBufferFunctions( 445 num_buffers, get_vp9_one_less_byte_frame_buffer, 446 release_vp9_frame_buffer)); 447 ASSERT_EQ(VPX_CODEC_MEM_ERROR, DecodeOneFrame()); 448 } 449 450 TEST_F(ExternalFrameBufferTest, NullGetFunction) { 451 const int num_buffers = VP9_MAXIMUM_REF_BUFFERS + VPX_MAXIMUM_WORK_BUFFERS; 452 ASSERT_EQ( 453 VPX_CODEC_INVALID_PARAM, 454 SetFrameBufferFunctions(num_buffers, NULL, release_vp9_frame_buffer)); 455 } 456 457 TEST_F(ExternalFrameBufferTest, NullReleaseFunction) { 458 const int num_buffers = VP9_MAXIMUM_REF_BUFFERS + VPX_MAXIMUM_WORK_BUFFERS; 459 ASSERT_EQ(VPX_CODEC_INVALID_PARAM, 460 SetFrameBufferFunctions(num_buffers, get_vp9_frame_buffer, NULL)); 461 } 462 463 TEST_F(ExternalFrameBufferTest, SetAfterDecode) { 464 const int num_buffers = VP9_MAXIMUM_REF_BUFFERS + VPX_MAXIMUM_WORK_BUFFERS; 465 ASSERT_EQ(VPX_CODEC_OK, DecodeOneFrame()); 466 ASSERT_EQ(VPX_CODEC_ERROR, 467 SetFrameBufferFunctions(num_buffers, get_vp9_frame_buffer, 468 release_vp9_frame_buffer)); 469 } 470 #endif // CONFIG_WEBM_IO 471 472 VP9_INSTANTIATE_TEST_CASE( 473 ExternalFrameBufferMD5Test, 474 ::testing::ValuesIn(libvpx_test::kVP9TestVectors, 475 libvpx_test::kVP9TestVectors + 476 libvpx_test::kNumVP9TestVectors)); 477 } // namespace 478