From fbada948fa345e67acf9aa41a8f9a78f5dfe8648 Mon Sep 17 00:00:00 2001 From: Frank Galligan Date: Tue, 10 Dec 2013 17:22:41 -0800 Subject: [PATCH] Add frame buffer lru cache. Add an option for libvpx to return the least recently used frame buffer. Change-Id: I886a96ffb94984f1c42de53086e0131922df3260 --- test/lru_frame_buffer_test.cc | 207 ++++++++++++++++++++++++++++++++++++++++++ test/test.mk | 1 + vp9/common/vp9_alloccommon.c | 6 ++ vp9/common/vp9_onyxc_int.h | 30 ++++-- vp9/vp9_dx_iface.c | 23 +++++ vpx/vp8dx.h | 8 ++ vpxdec.c | 16 +++- 7 files changed, 283 insertions(+), 8 deletions(-) create mode 100644 test/lru_frame_buffer_test.cc diff --git a/test/lru_frame_buffer_test.cc b/test/lru_frame_buffer_test.cc new file mode 100644 index 0000000..cd6b432 --- /dev/null +++ b/test/lru_frame_buffer_test.cc @@ -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 +#include + +#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 { + 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(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 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 diff --git a/test/test.mk b/test/test.mk index 63df488..361a34f 100644 --- a/test/test.mk +++ b/test/test.mk @@ -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 diff --git a/vp9/common/vp9_alloccommon.c b/vp9/common/vp9_alloccommon.c index 6c333c5..80c48d1 100644 --- a/vp9/common/vp9_alloccommon.c +++ b/vp9/common/vp9_alloccommon.c @@ -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() { diff --git a/vp9/common/vp9_onyxc_int.h b/vp9/common/vp9_onyxc_int.h index aa70868..bfb94e4 100644 --- a/vp9/common/vp9_onyxc_int.h +++ b/vp9/common/vp9_onyxc_int.h @@ -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) { diff --git a/vp9/vp9_dx_iface.c b/vp9/vp9_dx_iface.c index 7fdd5ef..c123c46 100644 --- a/vp9/vp9_dx_iface.c +++ b/vp9/vp9_dx_iface.c @@ -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}, }; diff --git a/vpx/vp8dx.h b/vpx/vp8dx.h index b457b93..218f0b8 100644 --- a/vpx/vp8dx.h +++ b/vpx/vp8dx.h @@ -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 */ diff --git a/vpxdec.c b/vpxdec.c index 01e0da2..91d8faf 100644 --- 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; -- 2.7.4