ref: fbada948fa345e67acf9aa41a8f9a78f5dfe8648
parent: d0ee1fd797598fd692cbfe410bc37308a9e8924e
author: Frank Galligan <[email protected]>
date: Tue Dec 10 12:22:41 EST 2013
Add frame buffer lru cache. Add an option for libvpx to return the least recently used frame buffer. Change-Id: I886a96ffb94984f1c42de53086e0131922df3260
--- /dev/null
+++ b/test/lru_frame_buffer_test.cc
@@ -1,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
--- a/test/test.mk
+++ b/test/test.mk
@@ -35,6 +35,7 @@
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
--- a/vp9/common/vp9_alloccommon.c
+++ b/vp9/common/vp9_alloccommon.c
@@ -144,6 +144,10 @@
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 @@
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() {
--- a/vp9/common/vp9_onyxc_int.h
+++ b/vp9/common/vp9_onyxc_int.h
@@ -218,6 +218,10 @@
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 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) {
--- a/vp9/vp9_dx_iface.c
+++ b/vp9/vp9_dx_iface.c
@@ -59,6 +59,7 @@
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 @@
} 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 @@
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 @@
{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},
};
--- a/vpx/vp8dx.h
+++ b/vpx/vp8dx.h
@@ -79,6 +79,13 @@
/** 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_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 */
--- a/vpxdec.c
+++ b/vpxdec.c
@@ -91,6 +91,8 @@
"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 *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 @@
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,8 +521,9 @@
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)) {
postproc = 1;
@@ -750,6 +754,14 @@
vpx_codec_error(&decoder));
return EXIT_FAILURE;
}
+ }
+
+ 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;