Add frame buffer lru cache.
authorFrank Galligan <fgalligan@google.com>
Wed, 11 Dec 2013 01:22:41 +0000 (17:22 -0800)
committerJames Zern <jzern@google.com>
Mon, 16 Dec 2013 03:57:42 +0000 (19:57 -0800)
Add an option for libvpx to return the least recently used
frame buffer.

Change-Id: I886a96ffb94984f1c42de53086e0131922df3260

test/lru_frame_buffer_test.cc [new file with mode: 0644]
test/test.mk
vp9/common/vp9_alloccommon.c
vp9/common/vp9_onyxc_int.h
vp9/vp9_dx_iface.c
vpx/vp8dx.h
vpxdec.c

diff --git a/test/lru_frame_buffer_test.cc b/test/lru_frame_buffer_test.cc
new file mode 100644 (file)
index 0000000..cd6b432
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ *  Copyright (c) 2013 The WebM project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <queue>
+#include <string>
+
+#include "test/codec_factory.h"
+#include "test/decode_test_driver.h"
+#include "test/ivf_video_source.h"
+#include "test/md5_helper.h"
+#include "test/util.h"
+#include "test/webm_video_source.h"
+
+namespace {
+
+const int kVideoNameParam = 1;
+
+const char *kLRUTestVectors[] = {
+  "vp90-2-02-size-lf-1920x1080.webm",
+  "vp90-2-05-resize.ivf",
+};
+
+// Callback used by libvpx to request the application to allocate a frame
+// buffer of at least |new_size| in bytes.
+int realloc_vp9_frame_buffer(void *user_priv, size_t new_size,
+                             vpx_codec_frame_buffer_t *fb) {
+  (void)user_priv;
+  if (fb == NULL)
+    return -1;
+
+  delete [] fb->data;
+  fb->data = new uint8_t[new_size];
+  fb->size = new_size;
+
+  return 0;
+}
+
+// Class for testing libvpx is using the least recently
+// used frame buffer when a new buffer is requested.
+class LRUFrameBufferTest
+    : public ::libvpx_test::DecoderTest,
+      public ::libvpx_test::CodecTestWithParam<const char*> {
+ protected:
+  struct FrameBufferMD5Sum {
+    int frame_buffer_index;
+    vpx_image_t img;
+    std::string md5;
+  };
+
+  LRUFrameBufferTest()
+      : DecoderTest(GET_PARAM(::libvpx_test::kCodecFactoryParam)),
+        num_buffers_(0),
+        num_jitter_buffers_(0),
+        frame_buffers_(NULL) {}
+
+  virtual ~LRUFrameBufferTest() {
+    for (int i = 0; i < num_buffers_; ++i) {
+      delete [] frame_buffers_[i].data;
+    }
+    delete [] frame_buffers_;
+  }
+
+  virtual void PreDecodeFrameHook(
+      const libvpx_test::CompressedVideoSource &video,
+      libvpx_test::Decoder *decoder) {
+    // Use external buffers for testing jitter buffers.
+    if (num_jitter_buffers_ > 0 && video.frame_number() == 0) {
+      const int max_reference_buffers = 8;
+
+      // Add 1 for a work buffer.
+      num_buffers_ = max_reference_buffers + 1 + num_jitter_buffers_;
+
+      // Have libvpx use frame buffers we create.
+      frame_buffers_ = new vpx_codec_frame_buffer_t[num_buffers_];
+      memset(frame_buffers_, 0, sizeof(frame_buffers_[0]) * num_buffers_);
+
+      decoder->SetExternalFrameBuffers(frame_buffers_, num_buffers_,
+                                       realloc_vp9_frame_buffer, NULL);
+    }
+
+    // Turn on frame buffer LRU cache.
+    decoder->Control(VP9D_SET_FRAME_BUFFER_LRU_CACHE, 1);
+  }
+
+  virtual void DecompressedFrameHook(const vpx_image_t &img,
+                                     const unsigned int frame_number) {
+    const uint32_t ximg_y_plane = 0;
+    const uint8_t *const y_buffer = img.planes[ximg_y_plane];
+
+    // Find which external buffer contains the y_buffer.
+    int i = 0;
+    for (i = 0; i < num_buffers_; ++i) {
+      if (y_buffer >= frame_buffers_[i].data &&
+          y_buffer < (frame_buffers_[i].data + frame_buffers_[i].size)) {
+        break;
+      }
+    }
+
+    FrameBufferMD5Sum fb_md5;
+    fb_md5.frame_buffer_index = i;
+    fb_md5.img = img;
+
+    libvpx_test::MD5 md5;
+    md5.Add(&img);
+    fb_md5.md5 = md5.Get();
+    jitter_buffer_md5_sums_.push(fb_md5);
+
+    // Check to see if any of the reconstructed image changed.
+    if (jitter_buffer_md5_sums_.size() >
+        static_cast<size_t>(num_jitter_buffers_)) {
+      fb_md5 = jitter_buffer_md5_sums_.front();
+
+      libvpx_test::MD5 md5;
+      md5.Add(&fb_md5.img);
+      const std::string check_str = md5.Get();
+
+      ASSERT_EQ(fb_md5.md5, check_str);
+      jitter_buffer_md5_sums_.pop();
+    }
+  }
+
+  libvpx_test::CompressedVideoSource *OpenCompressedFile(
+      const std::string &filename) {
+    if (filename.substr(filename.length() - 3, 3) == "ivf") {
+      return new libvpx_test::IVFVideoSource(filename);
+    } else if (filename.substr(filename.length() - 4, 4) == "webm") {
+      return new libvpx_test::WebMVideoSource(filename);
+    }
+    return NULL;
+  }
+
+  void set_num_jitter_buffers(int num_buffers) {
+    num_jitter_buffers_ = num_buffers;
+  }
+
+ private:
+  // Total number of external frame buffers.
+  int num_buffers_;
+  int num_jitter_buffers_;
+
+  // External frame buffers used by libvpx.
+  vpx_codec_frame_buffer_t *frame_buffers_;
+
+  // Save the md5 checksums for later comparison.
+  std::queue<FrameBufferMD5Sum> jitter_buffer_md5_sums_;
+};
+
+// This test runs through a set of test vectors, and decodes them.
+// Libvpx will call into the application to allocate a frame buffer when
+// needed. The md5 checksums are computed for each frame after it is
+// decoded and stored to be checked later. After a jitter frame buffer
+// has expired, the md5 checksum is computed again for the expired jitter
+// buffer frame and checked against the md5 checksum after the frame was
+// decoded. If md5 checksums match, then the test is passed. Otherwise,
+// the test failed.
+TEST_P(LRUFrameBufferTest, CheckLRUOneJitterBuffer) {
+  const std::string filename = GET_PARAM(kVideoNameParam);
+
+  set_num_jitter_buffers(1);
+
+  libvpx_test::CompressedVideoSource *const video =
+      OpenCompressedFile(filename);
+  video->Init();
+
+  // Decode frame, and check the md5 matching.
+  ASSERT_NO_FATAL_FAILURE(RunLoop(video));
+  delete video;
+}
+
+TEST_P(LRUFrameBufferTest, CheckLRUFourJitterBuffers) {
+  const std::string filename = GET_PARAM(kVideoNameParam);
+
+  set_num_jitter_buffers(4);
+
+  libvpx_test::CompressedVideoSource *const video =
+      OpenCompressedFile(filename);
+  video->Init();
+
+  // Decode frame, and check the md5 matching.
+  ASSERT_NO_FATAL_FAILURE(RunLoop(video));
+  delete video;
+}
+
+TEST_P(LRUFrameBufferTest, CheckLRUEightJitterBuffers) {
+  const std::string filename = GET_PARAM(kVideoNameParam);
+
+  set_num_jitter_buffers(8);
+
+  libvpx_test::CompressedVideoSource *const video =
+      OpenCompressedFile(filename);
+  video->Init();
+
+  // Decode frame, and check the md5 matching.
+  ASSERT_NO_FATAL_FAILURE(RunLoop(video));
+  delete video;
+}
+
+VP9_INSTANTIATE_TEST_CASE(LRUFrameBufferTest,
+                          ::testing::ValuesIn(kLRUTestVectors));
+}  // namespace
index 63df488..361a34f 100644 (file)
@@ -35,6 +35,7 @@ LIBVPX_TEST_SRCS-yes                   += decode_test_driver.cc
 LIBVPX_TEST_SRCS-yes                   += decode_test_driver.h
 LIBVPX_TEST_SRCS-$(CONFIG_DECODERS)    += ivf_video_source.h
 LIBVPX_TEST_SRCS-$(CONFIG_VP9_DECODER) += external_frame_buffer_test.cc
+LIBVPX_TEST_SRCS-$(CONFIG_VP9_DECODER) += lru_frame_buffer_test.cc
 
 ## WebM Parsing
 NESTEGG_SRCS                           += ../nestegg/halloc/halloc.h
index 6c333c5..80c48d1 100644 (file)
@@ -144,6 +144,10 @@ int vp9_alloc_frame_buffers(VP9_COMMON *cm, int width, int height) {
                     vpx_calloc(cm->fb_count, sizeof(*cm->yv12_fb)));
     CHECK_MEM_ERROR(cm, cm->fb_idx_ref_cnt,
                     vpx_calloc(cm->fb_count, sizeof(*cm->fb_idx_ref_cnt)));
+    if (cm->fb_lru) {
+      CHECK_MEM_ERROR(cm, cm->fb_idx_ref_lru,
+                      vpx_calloc(cm->fb_count, sizeof(*cm->fb_idx_ref_lru)));
+    }
   }
 
   vp9_free_frame_buffers(cm);
@@ -214,9 +218,11 @@ void vp9_remove_common(VP9_COMMON *cm) {
 
   vpx_free(cm->yv12_fb);
   vpx_free(cm->fb_idx_ref_cnt);
+  vpx_free(cm->fb_idx_ref_lru);
 
   cm->yv12_fb = NULL;
   cm->fb_idx_ref_cnt = NULL;
+  cm->fb_idx_ref_lru = NULL;
 }
 
 void vp9_initialize_common() {
index aa70868..bfb94e4 100644 (file)
@@ -218,6 +218,10 @@ typedef struct VP9Common {
   int fb_count;  // Total number of frame buffers
   vpx_realloc_frame_buffer_cb_fn_t realloc_fb_cb;
   void *user_priv;  // Private data associated with the external frame buffers.
+
+  int fb_lru;  // Flag telling if lru is on/off
+  uint32_t *fb_idx_ref_lru;  // Frame buffer lru cache
+  uint32_t fb_idx_ref_lru_count;
 } VP9_COMMON;
 
 // ref == 0 => LAST_FRAME
@@ -233,13 +237,27 @@ static YV12_BUFFER_CONFIG *get_frame_new_buffer(VP9_COMMON *cm) {
 
 static int get_free_fb(VP9_COMMON *cm) {
   int i;
-  for (i = 0; i < cm->fb_count; i++)
-    if (cm->fb_idx_ref_cnt[i] == 0)
-      break;
+  uint32_t lru_count = cm->fb_idx_ref_lru_count + 1;
+  int free_buffer_idx = cm->fb_count;
+  for (i = 0; i < cm->fb_count; i++) {
+    if (!cm->fb_lru) {
+      if (cm->fb_idx_ref_cnt[i] == 0) {
+        free_buffer_idx = i;
+        break;
+      }
+    } else {
+      if (cm->fb_idx_ref_cnt[i] == 0 && cm->fb_idx_ref_lru[i] < lru_count) {
+        free_buffer_idx = i;
+        lru_count = cm->fb_idx_ref_lru[i];
+      }
+    }
+  }
 
-  assert(i < cm->fb_count);
-  cm->fb_idx_ref_cnt[i] = 1;
-  return i;
+  assert(free_buffer_idx < cm->fb_count);
+  cm->fb_idx_ref_cnt[free_buffer_idx] = 1;
+  if (cm->fb_lru)
+    cm->fb_idx_ref_lru[free_buffer_idx] = ++cm->fb_idx_ref_lru_count;
+  return free_buffer_idx;
 }
 
 static void ref_cnt_fb(int *buf, int *idx, int new_idx) {
index 7fdd5ef..c123c46 100644 (file)
@@ -59,6 +59,7 @@ struct vpx_codec_alg_priv {
   int                     img_setup;
   int                     img_avail;
   int                     invert_tile_order;
+  int                     fb_lru;
 
   /* External buffer info to save for VP9 common. */
   vpx_codec_frame_buffer_t *fb_list;  // External frame buffers
@@ -327,10 +328,16 @@ static vpx_codec_err_t decode_one(vpx_codec_alg_priv_t  *ctx,
         } else {
           cm->fb_count = FRAME_BUFFERS;
         }
+        cm->fb_lru = ctx->fb_lru;
         CHECK_MEM_ERROR(cm, cm->yv12_fb,
                         vpx_calloc(cm->fb_count, sizeof(*cm->yv12_fb)));
         CHECK_MEM_ERROR(cm, cm->fb_idx_ref_cnt,
                         vpx_calloc(cm->fb_count, sizeof(*cm->fb_idx_ref_cnt)));
+        if (cm->fb_lru) {
+          CHECK_MEM_ERROR(cm, cm->fb_idx_ref_lru,
+                          vpx_calloc(cm->fb_count,
+                                     sizeof(*cm->fb_idx_ref_lru)));
+        }
         ctx->pbi = optr;
       }
     }
@@ -737,6 +744,21 @@ static vpx_codec_err_t set_invert_tile_order(vpx_codec_alg_priv_t *ctx,
   return VPX_CODEC_OK;
 }
 
+static vpx_codec_err_t set_frame_buffer_lru_cache(vpx_codec_alg_priv_t *ctx,
+                                                  int ctr_id,
+                                                  va_list args) {
+  VP9D_COMP *const pbi = (VP9D_COMP*)ctx->pbi;
+
+  // Save for later to pass into vp9 common.
+  ctx->fb_lru = va_arg(args, int);
+
+  if (pbi) {
+    VP9_COMMON *const cm = &pbi->common;
+    cm->fb_lru = ctx->fb_lru;
+  }
+  return VPX_CODEC_OK;
+}
+
 static vpx_codec_ctrl_fn_map_t ctf_maps[] = {
   {VP8_SET_REFERENCE,             set_reference},
   {VP8_COPY_REFERENCE,            copy_reference},
@@ -750,6 +772,7 @@ static vpx_codec_ctrl_fn_map_t ctf_maps[] = {
   {VP9_GET_REFERENCE,             get_reference},
   {VP9D_GET_DISPLAY_SIZE,         get_display_size},
   {VP9_INVERT_TILE_DECODE_ORDER,  set_invert_tile_order},
+  {VP9D_SET_FRAME_BUFFER_LRU_CACHE, set_frame_buffer_lru_cache},
   { -1, NULL},
 };
 
index b457b93..218f0b8 100644 (file)
@@ -79,6 +79,13 @@ enum vp8_dec_control_id {
   /** For testing. */
   VP9_INVERT_TILE_DECODE_ORDER,
 
+  /** control function to set the vp9 decoder into using the least recently
+   * used frame buffer when a new buffer is requested. Takes an int and if
+   * the value is zero will turn off using lru cache. The value of zero is
+   * the default. If the value is anything besides zero, then that will turn
+   * on lru cache.*/
+  VP9D_SET_FRAME_BUFFER_LRU_CACHE,
+
   VP8_DECODER_CTRL_ID_MAX
 };
 
@@ -110,6 +117,7 @@ VPX_CTRL_USE_TYPE(VP8D_GET_LAST_REF_USED,      int *)
 VPX_CTRL_USE_TYPE(VP8D_SET_DECRYPTOR,          vp8_decrypt_init *)
 VPX_CTRL_USE_TYPE(VP9D_GET_DISPLAY_SIZE,       int *)
 VPX_CTRL_USE_TYPE(VP9_INVERT_TILE_DECODE_ORDER, int)
+VPX_CTRL_USE_TYPE(VP9D_SET_FRAME_BUFFER_LRU_CACHE, int)
 
 /*! @} - end defgroup vp8_decoder */
 
index 01e0da2..91d8faf 100644 (file)
--- a/vpxdec.c
+++ b/vpxdec.c
@@ -91,6 +91,8 @@ static const arg_def_t scalearg = ARG_DEF("S", "scale", 0,
                                             "Scale output frames uniformly");
 static const arg_def_t fb_arg =
     ARG_DEF(NULL, "frame-buffers", 1, "Number of frame buffers to use");
+static const arg_def_t fb_lru_arg =
+    ARG_DEF(NULL, "frame-buffers-lru", 1, "Turn on/off frame buffer lru");
 
 
 #if CONFIG_MD5
@@ -100,7 +102,7 @@ static const arg_def_t md5arg = ARG_DEF(NULL, "md5", 0,
 static const arg_def_t *all_args[] = {
   &codecarg, &use_yv12, &use_i420, &flipuvarg, &noblitarg,
   &progressarg, &limitarg, &skiparg, &postprocarg, &summaryarg, &outputfile,
-  &threadsarg, &verbosearg, &scalearg, &fb_arg,
+  &threadsarg, &verbosearg, &scalearg, &fb_arg, &fb_lru_arg,
 #if CONFIG_MD5
   &md5arg,
 #endif
@@ -456,6 +458,7 @@ int main_loop(int argc, const char **argv_) {
   vpx_image_t             *scaled_img = NULL;
   int                     frame_avail, got_data;
   int                     num_external_frame_buffers = 0;
+  int                     fb_lru_cache = 0;
   vpx_codec_frame_buffer_t *frame_buffers = NULL;
 
   struct VpxDecInputContext input = {0};
@@ -518,7 +521,8 @@ int main_loop(int argc, const char **argv_) {
       do_scale = 1;
     else if (arg_match(&arg, &fb_arg, argi))
       num_external_frame_buffers = arg_parse_uint(&arg);
-
+    else if (arg_match(&arg, &fb_lru_arg, argi))
+      fb_lru_cache = arg_parse_uint(&arg);
 
 #if CONFIG_VP8_DECODER
     else if (arg_match(&arg, &addnoise_level, argi)) {
@@ -752,6 +756,14 @@ int main_loop(int argc, const char **argv_) {
     }
   }
 
+  if (fb_lru_cache > 0 &&
+      vpx_codec_control(&decoder, VP9D_SET_FRAME_BUFFER_LRU_CACHE,
+                        fb_lru_cache)) {
+    fprintf(stderr, "Failed to set frame buffer lru cache: %s\n",
+            vpx_codec_error(&decoder));
+    return EXIT_FAILURE;
+  }
+
   frame_avail = 1;
   got_data = 0;