Add frame buffer lru cache.

Add an option for libvpx to return the least recently used
frame buffer.

Change-Id: I886a96ffb94984f1c42de53086e0131922df3260
This commit is contained in:
Frank Galligan 2013-12-10 17:22:41 -08:00 committed by James Zern
parent d0ee1fd797
commit fbada948fa
7 changed files with 283 additions and 8 deletions

View File

@ -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

View 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

View 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() {

View 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) {

View 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},
};

View 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 */

View File

@ -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;