external_frame_buffer_test.cc (18928B)
1 /* 2 * Copyright (c) 2018, Alliance for Open Media. All rights reserved. 3 * 4 * This source code is subject to the terms of the BSD 2 Clause License and 5 * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License 6 * was not distributed with this source code in the LICENSE file, you can 7 * obtain it at www.aomedia.org/license/software. If the Alliance for Open 8 * Media Patent License 1.0 was not distributed with this source code in the 9 * PATENTS file, you can obtain it at www.aomedia.org/license/patent. 10 */ 11 12 #include <memory> 13 #include <string> 14 #include "common/tools_common.h" 15 #include "config/aom_config.h" 16 #include "test/codec_factory.h" 17 #include "test/decode_test_driver.h" 18 #include "test/ivf_video_source.h" 19 #include "test/md5_helper.h" 20 #include "test/test_vectors.h" 21 #include "test/util.h" 22 #if CONFIG_WEBM_IO 23 #include "test/webm_video_source.h" 24 #endif 25 26 namespace { 27 28 const int kVideoNameParam = 1; 29 30 struct ExternalFrameBuffer { 31 uint8_t *data; 32 size_t size; 33 int in_use; 34 }; 35 36 // Class to manipulate a list of external frame buffers. 37 class ExternalFrameBufferList { 38 public: 39 ExternalFrameBufferList() 40 : num_buffers_(0), num_used_buffers_(0), ext_fb_list_(nullptr) {} 41 42 virtual ~ExternalFrameBufferList() { 43 for (int i = 0; i < num_buffers_; ++i) { 44 delete[] ext_fb_list_[i].data; 45 } 46 delete[] ext_fb_list_; 47 } 48 49 // Creates the list to hold the external buffers. Returns true on success. 50 bool CreateBufferList(int num_buffers) { 51 if (num_buffers < 0) return false; 52 53 num_buffers_ = num_buffers; 54 ext_fb_list_ = new ExternalFrameBuffer[num_buffers_]; 55 if (ext_fb_list_ == nullptr) { 56 EXPECT_NE(ext_fb_list_, nullptr); 57 return false; 58 } 59 memset(ext_fb_list_, 0, sizeof(ext_fb_list_[0]) * num_buffers_); 60 return true; 61 } 62 63 // Searches the frame buffer list for a free frame buffer. Makes sure 64 // that the frame buffer is at least |min_size| in bytes. Marks that the 65 // frame buffer is in use by libaom. Finally sets |fb| to point to the 66 // external frame buffer. Returns < 0 on an error. 67 int GetFreeFrameBuffer(size_t min_size, aom_codec_frame_buffer_t *fb) { 68 EXPECT_NE(fb, nullptr); 69 const int idx = FindFreeBufferIndex(); 70 if (idx == num_buffers_) return -1; 71 72 if (ext_fb_list_[idx].size < min_size) { 73 delete[] ext_fb_list_[idx].data; 74 ext_fb_list_[idx].data = new uint8_t[min_size]; 75 if (ext_fb_list_[idx].data == nullptr) { 76 EXPECT_NE(ext_fb_list_[idx].data, nullptr); 77 } 78 memset(ext_fb_list_[idx].data, 0, min_size); 79 ext_fb_list_[idx].size = min_size; 80 } 81 82 SetFrameBuffer(idx, fb); 83 84 num_used_buffers_++; 85 return 0; 86 } 87 88 // Test function that will not allocate any data for the frame buffer. 89 // Returns < 0 on an error. 90 int GetZeroFrameBuffer(size_t min_size, aom_codec_frame_buffer_t *fb) { 91 EXPECT_NE(fb, nullptr); 92 const int idx = FindFreeBufferIndex(); 93 if (idx == num_buffers_) return -1; 94 95 if (ext_fb_list_[idx].size < min_size) { 96 delete[] ext_fb_list_[idx].data; 97 ext_fb_list_[idx].data = nullptr; 98 ext_fb_list_[idx].size = min_size; 99 } 100 101 SetFrameBuffer(idx, fb); 102 return 0; 103 } 104 105 // Marks the external frame buffer that |fb| is pointing to as free. 106 // Returns < 0 on an error. 107 int ReturnFrameBuffer(aom_codec_frame_buffer_t *fb) { 108 if (fb == nullptr) { 109 EXPECT_NE(fb, nullptr); 110 return -1; 111 } 112 ExternalFrameBuffer *const ext_fb = 113 reinterpret_cast<ExternalFrameBuffer *>(fb->priv); 114 if (ext_fb == nullptr) { 115 EXPECT_NE(ext_fb, nullptr); 116 return -1; 117 } 118 EXPECT_EQ(1, ext_fb->in_use); 119 ext_fb->in_use = 0; 120 num_used_buffers_--; 121 return 0; 122 } 123 124 // Checks that the aom_image_t data is contained within the external frame 125 // buffer private data passed back in the aom_image_t. 126 void CheckImageFrameBuffer(const aom_image_t *img) { 127 const struct ExternalFrameBuffer *const ext_fb = 128 reinterpret_cast<ExternalFrameBuffer *>(img->fb_priv); 129 130 ASSERT_TRUE(img->planes[0] >= ext_fb->data && 131 img->planes[0] < (ext_fb->data + ext_fb->size)); 132 } 133 134 int num_used_buffers() const { return num_used_buffers_; } 135 136 private: 137 // Returns the index of the first free frame buffer. Returns |num_buffers_| 138 // if there are no free frame buffers. 139 int FindFreeBufferIndex() { 140 int i; 141 // Find a free frame buffer. 142 for (i = 0; i < num_buffers_; ++i) { 143 if (!ext_fb_list_[i].in_use) break; 144 } 145 return i; 146 } 147 148 // Sets |fb| to an external frame buffer. idx is the index into the frame 149 // buffer list. 150 void SetFrameBuffer(int idx, aom_codec_frame_buffer_t *fb) { 151 ASSERT_NE(fb, nullptr); 152 fb->data = ext_fb_list_[idx].data; 153 fb->size = ext_fb_list_[idx].size; 154 ASSERT_EQ(0, ext_fb_list_[idx].in_use); 155 ext_fb_list_[idx].in_use = 1; 156 fb->priv = &ext_fb_list_[idx]; 157 } 158 159 int num_buffers_; 160 int num_used_buffers_; 161 ExternalFrameBuffer *ext_fb_list_; 162 }; 163 164 #if CONFIG_WEBM_IO 165 166 // Callback used by libaom to request the application to return a frame 167 // buffer of at least |min_size| in bytes. 168 int get_aom_frame_buffer(void *user_priv, size_t min_size, 169 aom_codec_frame_buffer_t *fb) { 170 ExternalFrameBufferList *const fb_list = 171 reinterpret_cast<ExternalFrameBufferList *>(user_priv); 172 return fb_list->GetFreeFrameBuffer(min_size, fb); 173 } 174 175 // Callback used by libaom to tell the application that |fb| is not needed 176 // anymore. 177 int release_aom_frame_buffer(void *user_priv, aom_codec_frame_buffer_t *fb) { 178 ExternalFrameBufferList *const fb_list = 179 reinterpret_cast<ExternalFrameBufferList *>(user_priv); 180 return fb_list->ReturnFrameBuffer(fb); 181 } 182 183 // Callback will not allocate data for frame buffer. 184 int get_aom_zero_frame_buffer(void *user_priv, size_t min_size, 185 aom_codec_frame_buffer_t *fb) { 186 ExternalFrameBufferList *const fb_list = 187 reinterpret_cast<ExternalFrameBufferList *>(user_priv); 188 return fb_list->GetZeroFrameBuffer(min_size, fb); 189 } 190 191 // Callback will allocate one less byte than |min_size|. 192 int get_aom_one_less_byte_frame_buffer(void *user_priv, size_t min_size, 193 aom_codec_frame_buffer_t *fb) { 194 ExternalFrameBufferList *const fb_list = 195 reinterpret_cast<ExternalFrameBufferList *>(user_priv); 196 return fb_list->GetFreeFrameBuffer(min_size - 1, fb); 197 } 198 199 // Callback will not release the external frame buffer. 200 int do_not_release_aom_frame_buffer(void *user_priv, 201 aom_codec_frame_buffer_t *fb) { 202 (void)user_priv; 203 (void)fb; 204 return 0; 205 } 206 207 #endif // CONFIG_WEBM_IO 208 209 // Class for testing passing in external frame buffers to libaom. 210 class ExternalFrameBufferMD5Test 211 : public ::libaom_test::DecoderTest, 212 public ::libaom_test::CodecTestWithParam<const char *> { 213 protected: 214 ExternalFrameBufferMD5Test() 215 : DecoderTest(GET_PARAM(::libaom_test::kCodecFactoryParam)), 216 md5_file_(nullptr), num_buffers_(0) {} 217 218 ~ExternalFrameBufferMD5Test() override { 219 if (md5_file_ != nullptr) fclose(md5_file_); 220 } 221 222 void PreDecodeFrameHook(const libaom_test::CompressedVideoSource &video, 223 libaom_test::Decoder *decoder) override { 224 if (num_buffers_ > 0 && video.frame_number() == 0) { 225 // Have libaom use frame buffers we create. 226 ASSERT_TRUE(fb_list_.CreateBufferList(num_buffers_)); 227 ASSERT_EQ(AOM_CODEC_OK, 228 decoder->SetFrameBufferFunctions(GetAV1FrameBuffer, 229 ReleaseAV1FrameBuffer, this)); 230 } 231 } 232 233 void OpenMD5File(const std::string &md5_file_name_) { 234 md5_file_ = libaom_test::OpenTestDataFile(md5_file_name_); 235 ASSERT_NE(md5_file_, nullptr) 236 << "Md5 file open failed. Filename: " << md5_file_name_; 237 } 238 239 void DecompressedFrameHook(const aom_image_t &img, 240 const unsigned int frame_number) override { 241 ASSERT_NE(md5_file_, nullptr); 242 char expected_md5[33]; 243 char junk[128]; 244 245 // Read correct md5 checksums. 246 const int res = fscanf(md5_file_, "%s %s", expected_md5, junk); 247 ASSERT_NE(EOF, res) << "Read md5 data failed"; 248 expected_md5[32] = '\0'; 249 250 ::libaom_test::MD5 md5_res; 251 #if FORCE_HIGHBITDEPTH_DECODING 252 const aom_img_fmt_t shifted_fmt = 253 (aom_img_fmt)(img.fmt & ~AOM_IMG_FMT_HIGHBITDEPTH); 254 if (img.bit_depth == 8 && shifted_fmt != img.fmt) { 255 aom_image_t *img_shifted = 256 aom_img_alloc(nullptr, shifted_fmt, img.d_w, img.d_h, 16); 257 img_shifted->bit_depth = img.bit_depth; 258 img_shifted->monochrome = img.monochrome; 259 aom_img_downshift(img_shifted, &img, 0); 260 md5_res.Add(img_shifted); 261 aom_img_free(img_shifted); 262 } else { 263 #endif 264 md5_res.Add(&img); 265 #if FORCE_HIGHBITDEPTH_DECODING 266 } 267 #endif 268 const char *const actual_md5 = md5_res.Get(); 269 270 // Check md5 match. 271 ASSERT_STREQ(expected_md5, actual_md5) 272 << "Md5 checksums don't match: frame number = " << frame_number; 273 274 const struct ExternalFrameBuffer *const ext_fb = 275 reinterpret_cast<ExternalFrameBuffer *>(img.fb_priv); 276 277 ASSERT_TRUE(img.planes[0] >= ext_fb->data && 278 img.planes[0] < (ext_fb->data + ext_fb->size)); 279 } 280 281 // Callback to get a free external frame buffer. Return value < 0 is an 282 // error. 283 static int GetAV1FrameBuffer(void *user_priv, size_t min_size, 284 aom_codec_frame_buffer_t *fb) { 285 ExternalFrameBufferMD5Test *const md5Test = 286 reinterpret_cast<ExternalFrameBufferMD5Test *>(user_priv); 287 return md5Test->fb_list_.GetFreeFrameBuffer(min_size, fb); 288 } 289 290 // Callback to release an external frame buffer. Return value < 0 is an 291 // error. 292 static int ReleaseAV1FrameBuffer(void *user_priv, 293 aom_codec_frame_buffer_t *fb) { 294 ExternalFrameBufferMD5Test *const md5Test = 295 reinterpret_cast<ExternalFrameBufferMD5Test *>(user_priv); 296 return md5Test->fb_list_.ReturnFrameBuffer(fb); 297 } 298 299 void set_num_buffers(int num_buffers) { num_buffers_ = num_buffers; } 300 int num_buffers() const { return num_buffers_; } 301 302 private: 303 FILE *md5_file_; 304 int num_buffers_; 305 ExternalFrameBufferList fb_list_; 306 }; 307 308 #if CONFIG_WEBM_IO 309 const char kAV1TestFile[] = "av1-1-b8-03-sizeup.mkv"; 310 const char kAV1NonRefTestFile[] = "av1-1-b8-01-size-226x226.ivf"; 311 312 // Class for testing passing in external frame buffers to libaom. 313 class ExternalFrameBufferTest : public ::testing::Test { 314 protected: 315 ExternalFrameBufferTest() 316 : video_(nullptr), decoder_(nullptr), num_buffers_(0) {} 317 318 void SetUp() override { 319 video_ = new libaom_test::WebMVideoSource(kAV1TestFile); 320 ASSERT_NE(video_, nullptr); 321 video_->Init(); 322 video_->Begin(); 323 324 aom_codec_dec_cfg_t cfg = aom_codec_dec_cfg_t(); 325 cfg.allow_lowbitdepth = !FORCE_HIGHBITDEPTH_DECODING; 326 decoder_ = new libaom_test::AV1Decoder(cfg, 0); 327 ASSERT_NE(decoder_, nullptr); 328 } 329 330 void TearDown() override { 331 delete decoder_; 332 decoder_ = nullptr; 333 delete video_; 334 video_ = nullptr; 335 } 336 337 // Passes the external frame buffer information to libaom. 338 aom_codec_err_t SetFrameBufferFunctions( 339 int num_buffers, aom_get_frame_buffer_cb_fn_t cb_get, 340 aom_release_frame_buffer_cb_fn_t cb_release) { 341 if (num_buffers > 0) { 342 num_buffers_ = num_buffers; 343 EXPECT_TRUE(fb_list_.CreateBufferList(num_buffers_)); 344 } 345 346 return decoder_->SetFrameBufferFunctions(cb_get, cb_release, &fb_list_); 347 } 348 349 aom_codec_err_t DecodeOneFrame() { 350 const aom_codec_err_t res = 351 decoder_->DecodeFrame(video_->cxdata(), video_->frame_size()); 352 CheckDecodedFrames(); 353 if (res == AOM_CODEC_OK) video_->Next(); 354 return res; 355 } 356 357 aom_codec_err_t DecodeRemainingFrames() { 358 for (; video_->cxdata() != nullptr; video_->Next()) { 359 const aom_codec_err_t res = 360 decoder_->DecodeFrame(video_->cxdata(), video_->frame_size()); 361 if (res != AOM_CODEC_OK) return res; 362 CheckDecodedFrames(); 363 } 364 return AOM_CODEC_OK; 365 } 366 367 protected: 368 void CheckDecodedFrames() { 369 libaom_test::DxDataIterator dec_iter = decoder_->GetDxData(); 370 const aom_image_t *img = nullptr; 371 372 // Get decompressed data 373 while ((img = dec_iter.Next()) != nullptr) { 374 fb_list_.CheckImageFrameBuffer(img); 375 } 376 } 377 378 libaom_test::CompressedVideoSource *video_; 379 libaom_test::AV1Decoder *decoder_; 380 int num_buffers_; 381 ExternalFrameBufferList fb_list_; 382 }; 383 384 class ExternalFrameBufferNonRefTest : public ExternalFrameBufferTest { 385 protected: 386 void SetUp() override { 387 video_ = new libaom_test::IVFVideoSource(kAV1NonRefTestFile); 388 ASSERT_NE(video_, nullptr); 389 video_->Init(); 390 video_->Begin(); 391 392 aom_codec_dec_cfg_t cfg = aom_codec_dec_cfg_t(); 393 cfg.allow_lowbitdepth = !FORCE_HIGHBITDEPTH_DECODING; 394 decoder_ = new libaom_test::AV1Decoder(cfg, 0); 395 ASSERT_NE(decoder_, nullptr); 396 } 397 398 virtual void CheckFrameBufferRelease() { 399 TearDown(); 400 ASSERT_EQ(0, fb_list_.num_used_buffers()); 401 } 402 }; 403 #endif // CONFIG_WEBM_IO 404 405 // This test runs through the set of test vectors, and decodes them. 406 // Libaom will call into the application to allocate a frame buffer when 407 // needed. The md5 checksums are computed for each frame in the video file. 408 // If md5 checksums match the correct md5 data, then the test is passed. 409 // Otherwise, the test failed. 410 TEST_P(ExternalFrameBufferMD5Test, ExtFBMD5Match) { 411 const std::string filename = GET_PARAM(kVideoNameParam); 412 aom_codec_dec_cfg_t cfg = aom_codec_dec_cfg_t(); 413 414 // Number of buffers equals #AOM_MAXIMUM_REF_BUFFERS + 415 // #AOM_MAXIMUM_WORK_BUFFERS + four jitter buffers. 416 const int jitter_buffers = 4; 417 const int num_buffers = 418 AOM_MAXIMUM_REF_BUFFERS + AOM_MAXIMUM_WORK_BUFFERS + jitter_buffers; 419 set_num_buffers(num_buffers); 420 421 // Open compressed video file. 422 std::unique_ptr<libaom_test::CompressedVideoSource> video; 423 if (filename.substr(filename.length() - 3, 3) == "ivf") { 424 video.reset(new libaom_test::IVFVideoSource(filename)); 425 } else { 426 #if CONFIG_WEBM_IO 427 video.reset(new libaom_test::WebMVideoSource(filename)); 428 #else 429 fprintf(stderr, "WebM IO is disabled, skipping test vector %s\n", 430 filename.c_str()); 431 return; 432 #endif 433 } 434 ASSERT_NE(video, nullptr); 435 video->Init(); 436 437 // Construct md5 file name. 438 const std::string md5_filename = filename + ".md5"; 439 OpenMD5File(md5_filename); 440 441 // Set decode config. 442 cfg.allow_lowbitdepth = !FORCE_HIGHBITDEPTH_DECODING; 443 set_cfg(cfg); 444 445 // Decode frame, and check the md5 matching. 446 ASSERT_NO_FATAL_FAILURE(RunLoop(video.get(), cfg)); 447 } 448 449 #if CONFIG_WEBM_IO 450 TEST_F(ExternalFrameBufferTest, MinFrameBuffers) { 451 // Minimum number of external frame buffers for AV1 is 452 // #AOM_MAXIMUM_REF_BUFFERS + #AOM_MAXIMUM_WORK_BUFFERS. 453 const int num_buffers = AOM_MAXIMUM_REF_BUFFERS + AOM_MAXIMUM_WORK_BUFFERS; 454 ASSERT_EQ(AOM_CODEC_OK, 455 SetFrameBufferFunctions(num_buffers, get_aom_frame_buffer, 456 release_aom_frame_buffer)); 457 ASSERT_EQ(AOM_CODEC_OK, DecodeRemainingFrames()); 458 } 459 460 TEST_F(ExternalFrameBufferTest, EightJitterBuffers) { 461 // Number of buffers equals #AOM_MAXIMUM_REF_BUFFERS + 462 // #AOM_MAXIMUM_WORK_BUFFERS + eight jitter buffers. 463 const int jitter_buffers = 8; 464 const int num_buffers = 465 AOM_MAXIMUM_REF_BUFFERS + AOM_MAXIMUM_WORK_BUFFERS + jitter_buffers; 466 ASSERT_EQ(AOM_CODEC_OK, 467 SetFrameBufferFunctions(num_buffers, get_aom_frame_buffer, 468 release_aom_frame_buffer)); 469 ASSERT_EQ(AOM_CODEC_OK, DecodeRemainingFrames()); 470 } 471 472 TEST_F(ExternalFrameBufferTest, NotEnoughBuffers) { 473 // Minimum number of external frame buffers for AV1 is 474 // #AOM_MAXIMUM_REF_BUFFERS + #AOM_MAXIMUM_WORK_BUFFERS. Most files will 475 // only use 5 frame buffers at one time. 476 const int num_buffers = 2; 477 ASSERT_EQ(AOM_CODEC_OK, 478 SetFrameBufferFunctions(num_buffers, get_aom_frame_buffer, 479 release_aom_frame_buffer)); 480 ASSERT_EQ(AOM_CODEC_OK, DecodeOneFrame()); 481 // Only run this on long clips. Decoding a very short clip will return 482 // AOM_CODEC_OK even with only 2 buffers. 483 ASSERT_EQ(AOM_CODEC_MEM_ERROR, DecodeRemainingFrames()); 484 } 485 486 TEST_F(ExternalFrameBufferTest, NoRelease) { 487 const int num_buffers = AOM_MAXIMUM_REF_BUFFERS + AOM_MAXIMUM_WORK_BUFFERS; 488 ASSERT_EQ(AOM_CODEC_OK, 489 SetFrameBufferFunctions(num_buffers, get_aom_frame_buffer, 490 do_not_release_aom_frame_buffer)); 491 ASSERT_EQ(AOM_CODEC_OK, DecodeOneFrame()); 492 ASSERT_EQ(AOM_CODEC_MEM_ERROR, DecodeRemainingFrames()); 493 } 494 495 TEST_F(ExternalFrameBufferTest, NullRealloc) { 496 const int num_buffers = AOM_MAXIMUM_REF_BUFFERS + AOM_MAXIMUM_WORK_BUFFERS; 497 ASSERT_EQ(AOM_CODEC_OK, 498 SetFrameBufferFunctions(num_buffers, get_aom_zero_frame_buffer, 499 release_aom_frame_buffer)); 500 ASSERT_EQ(AOM_CODEC_MEM_ERROR, DecodeOneFrame()); 501 } 502 503 TEST_F(ExternalFrameBufferTest, ReallocOneLessByte) { 504 const int num_buffers = AOM_MAXIMUM_REF_BUFFERS + AOM_MAXIMUM_WORK_BUFFERS; 505 ASSERT_EQ(AOM_CODEC_OK, SetFrameBufferFunctions( 506 num_buffers, get_aom_one_less_byte_frame_buffer, 507 release_aom_frame_buffer)); 508 ASSERT_EQ(AOM_CODEC_MEM_ERROR, DecodeOneFrame()); 509 } 510 511 TEST_F(ExternalFrameBufferTest, NullGetFunction) { 512 const int num_buffers = AOM_MAXIMUM_REF_BUFFERS + AOM_MAXIMUM_WORK_BUFFERS; 513 ASSERT_EQ( 514 AOM_CODEC_INVALID_PARAM, 515 SetFrameBufferFunctions(num_buffers, nullptr, release_aom_frame_buffer)); 516 } 517 518 TEST_F(ExternalFrameBufferTest, NullReleaseFunction) { 519 const int num_buffers = AOM_MAXIMUM_REF_BUFFERS + AOM_MAXIMUM_WORK_BUFFERS; 520 ASSERT_EQ( 521 AOM_CODEC_INVALID_PARAM, 522 SetFrameBufferFunctions(num_buffers, get_aom_frame_buffer, nullptr)); 523 } 524 525 TEST_F(ExternalFrameBufferTest, SetAfterDecode) { 526 const int num_buffers = AOM_MAXIMUM_REF_BUFFERS + AOM_MAXIMUM_WORK_BUFFERS; 527 ASSERT_EQ(AOM_CODEC_OK, DecodeOneFrame()); 528 ASSERT_EQ(AOM_CODEC_ERROR, 529 SetFrameBufferFunctions(num_buffers, get_aom_frame_buffer, 530 release_aom_frame_buffer)); 531 } 532 533 TEST_F(ExternalFrameBufferNonRefTest, ReleaseNonRefFrameBuffer) { 534 const int num_buffers = AOM_MAXIMUM_REF_BUFFERS + AOM_MAXIMUM_WORK_BUFFERS; 535 ASSERT_EQ(AOM_CODEC_OK, 536 SetFrameBufferFunctions(num_buffers, get_aom_frame_buffer, 537 release_aom_frame_buffer)); 538 ASSERT_EQ(AOM_CODEC_OK, DecodeRemainingFrames()); 539 CheckFrameBufferRelease(); 540 } 541 #endif // CONFIG_WEBM_IO 542 543 AV1_INSTANTIATE_TEST_SUITE( 544 ExternalFrameBufferMD5Test, 545 ::testing::ValuesIn(libaom_test::kAV1TestVectors, 546 libaom_test::kAV1TestVectors + 547 libaom_test::kNumAV1TestVectors)); 548 } // namespace