ref: bfb6d488122a42b6c86e4af2f1ecd524fa1864e2
parent: 8710cceb45ad64a50287a555e8803df1df5e72ad
author: Frank Galligan <[email protected]>
date: Thu May 21 07:49:11 EDT 2015
Add control to skip loop filter in VP9 decoder. This control allows the application to skip the loop filter in the decoder. This is an advanced control that should only be used in extreme circumstances as it may introduce and accumulate decode artifacts. Change-Id: I278c65c60826f84c9141ebe06c6eeed3c2335fa8
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
xxxx-yy-zz v1.4.0 "Changes for next release"
vpxenc is changed to use VP9 by default.
Encoder controls added for 1 pass SVC.
+ Decoder control to toggle on/off loopfilter.
2015-04-03 v1.4.0 "Indian Runner Duck"
This release includes significant improvements to the VP9 codec.
--- a/test/test.mk
+++ b/test/test.mk
@@ -66,6 +66,7 @@
LIBVPX_TEST_SRCS-$(CONFIG_DECODERS) += ../webmdec.cc
LIBVPX_TEST_SRCS-$(CONFIG_DECODERS) += ../webmdec.h
LIBVPX_TEST_SRCS-$(CONFIG_DECODERS) += webm_video_source.h
+LIBVPX_TEST_SRCS-$(CONFIG_VP9_DECODER) += vp9_skip_loopfilter_test.cc
endif
LIBVPX_TEST_SRCS-$(CONFIG_DECODERS) += decode_api_test.cc
--- /dev/null
+++ b/test/vp9_skip_loopfilter_test.cc
@@ -1,0 +1,180 @@
+/*
+ * Copyright (c) 2015 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 <string>
+
+#include "test/codec_factory.h"
+#include "test/decode_test_driver.h"
+#include "test/md5_helper.h"
+#include "test/util.h"
+#include "test/webm_video_source.h"
+
+namespace {
+
+const char kVp9TestFile[] = "vp90-2-08-tile_1x8_frame_parallel.webm";
+const char kVp9Md5File[] = "vp90-2-08-tile_1x8_frame_parallel.webm.md5";
+
+// Class for testing shutting off the loop filter.
+class SkipLoopFilterTest {
+ public:
+ SkipLoopFilterTest()
+ : video_(NULL),
+ decoder_(NULL),
+ md5_file_(NULL) {}
+
+ ~SkipLoopFilterTest() {
+ if (md5_file_ != NULL)
+ fclose(md5_file_);
+ delete decoder_;
+ delete video_;
+ }
+
+ // If |threads| > 0 then set the decoder with that number of threads.
+ void Init(int num_threads) {
+ expected_md5_[0] = '\0';
+ junk_[0] = '\0';
+ video_ = new libvpx_test::WebMVideoSource(kVp9TestFile);
+ ASSERT_TRUE(video_ != NULL);
+ video_->Init();
+ video_->Begin();
+
+ vpx_codec_dec_cfg_t cfg = vpx_codec_dec_cfg_t();
+ if (num_threads > 0)
+ cfg.threads = num_threads;
+ decoder_ = new libvpx_test::VP9Decoder(cfg, 0);
+ ASSERT_TRUE(decoder_ != NULL);
+
+ OpenMd5File(kVp9Md5File);
+ }
+
+ // Set the VP9 skipLoopFilter control value.
+ void SetSkipLoopFilter(int value, vpx_codec_err_t expected_value) {
+ decoder_->Control(VP9_SET_SKIP_LOOP_FILTER, value, expected_value);
+ }
+
+ vpx_codec_err_t DecodeOneFrame() {
+ const vpx_codec_err_t res =
+ decoder_->DecodeFrame(video_->cxdata(), video_->frame_size());
+ if (res == VPX_CODEC_OK) {
+ ReadMd5();
+ video_->Next();
+ }
+ return res;
+ }
+
+ vpx_codec_err_t DecodeRemainingFrames() {
+ for (; video_->cxdata() != NULL; video_->Next()) {
+ const vpx_codec_err_t res =
+ decoder_->DecodeFrame(video_->cxdata(), video_->frame_size());
+ if (res != VPX_CODEC_OK)
+ return res;
+ ReadMd5();
+ }
+ return VPX_CODEC_OK;
+ }
+
+ // Checks if MD5 matches or doesn't.
+ void CheckMd5(bool matches) {
+ libvpx_test::DxDataIterator dec_iter = decoder_->GetDxData();
+ const vpx_image_t *img = dec_iter.Next();
+ CheckMd5Vpx(*img, matches);
+ }
+
+ private:
+ // TODO(fgalligan): Move the MD5 testing code into another class.
+ void OpenMd5File(const std::string &md5_file_name) {
+ md5_file_ = libvpx_test::OpenTestDataFile(md5_file_name);
+ ASSERT_TRUE(md5_file_ != NULL) << "MD5 file open failed. Filename: "
+ << md5_file_name;
+ }
+
+ // Reads the next line of the MD5 file.
+ void ReadMd5() {
+ ASSERT_TRUE(md5_file_ != NULL);
+ const int res = fscanf(md5_file_, "%s %s", expected_md5_, junk_);
+ ASSERT_NE(EOF, res) << "Read md5 data failed";
+ expected_md5_[32] = '\0';
+ }
+
+ // Checks if the last read MD5 matches |img| or doesn't.
+ void CheckMd5Vpx(const vpx_image_t &img, bool matches) {
+ ::libvpx_test::MD5 md5_res;
+ md5_res.Add(&img);
+ const char *const actual_md5 = md5_res.Get();
+
+ // Check MD5.
+ if (matches)
+ ASSERT_STREQ(expected_md5_, actual_md5) << "MD5 checksums don't match";
+ else
+ ASSERT_STRNE(expected_md5_, actual_md5) << "MD5 checksums match";
+ }
+
+ libvpx_test::WebMVideoSource *video_;
+ libvpx_test::VP9Decoder *decoder_;
+ FILE *md5_file_;
+ char expected_md5_[33];
+ char junk_[128];
+};
+
+TEST(SkipLoopFilterTest, ShutOffLoopFilter) {
+ const int non_zero_value = 1;
+ const int num_threads = 0;
+ SkipLoopFilterTest skip_loop_filter;
+ skip_loop_filter.Init(num_threads);
+ skip_loop_filter.SetSkipLoopFilter(non_zero_value, VPX_CODEC_OK);
+ ASSERT_EQ(VPX_CODEC_OK, skip_loop_filter.DecodeRemainingFrames());
+ skip_loop_filter.CheckMd5(false);
+}
+
+TEST(SkipLoopFilterTest, ShutOffLoopFilterSingleThread) {
+ const int non_zero_value = 1;
+ const int num_threads = 1;
+ SkipLoopFilterTest skip_loop_filter;
+ skip_loop_filter.Init(num_threads);
+ skip_loop_filter.SetSkipLoopFilter(non_zero_value, VPX_CODEC_OK);
+ ASSERT_EQ(VPX_CODEC_OK, skip_loop_filter.DecodeRemainingFrames());
+ skip_loop_filter.CheckMd5(false);
+}
+
+TEST(SkipLoopFilterTest, ShutOffLoopFilter8Threads) {
+ const int non_zero_value = 1;
+ const int num_threads = 8;
+ SkipLoopFilterTest skip_loop_filter;
+ skip_loop_filter.Init(num_threads);
+ skip_loop_filter.SetSkipLoopFilter(non_zero_value, VPX_CODEC_OK);
+ ASSERT_EQ(VPX_CODEC_OK, skip_loop_filter.DecodeRemainingFrames());
+ skip_loop_filter.CheckMd5(false);
+}
+
+TEST(SkipLoopFilterTest, WithLoopFilter) {
+ const int non_zero_value = 1;
+ const int num_threads = 0;
+ SkipLoopFilterTest skip_loop_filter;
+ skip_loop_filter.Init(num_threads);
+ skip_loop_filter.SetSkipLoopFilter(non_zero_value, VPX_CODEC_OK);
+ skip_loop_filter.SetSkipLoopFilter(0, VPX_CODEC_OK);
+ ASSERT_EQ(VPX_CODEC_OK, skip_loop_filter.DecodeRemainingFrames());
+ skip_loop_filter.CheckMd5(true);
+}
+
+TEST(SkipLoopFilterTest, ToggleLoopFilter) {
+ const int num_threads = 0;
+ SkipLoopFilterTest skip_loop_filter;
+ skip_loop_filter.Init(num_threads);
+
+ for (int i = 0; i < 10; ++i) {
+ skip_loop_filter.SetSkipLoopFilter(i % 2, VPX_CODEC_OK);
+ ASSERT_EQ(VPX_CODEC_OK, skip_loop_filter.DecodeOneFrame());
+ }
+ ASSERT_EQ(VPX_CODEC_OK, skip_loop_filter.DecodeRemainingFrames());
+ skip_loop_filter.CheckMd5(false);
+}
+
+} // namespace
--- a/vp9/common/vp9_onyxc_int.h
+++ b/vp9/common/vp9_onyxc_int.h
@@ -264,6 +264,7 @@
int log2_tile_cols, log2_tile_rows;
int byte_alignment;
+ int skip_loop_filter;
// Private data associated with the frame buffer callbacks.
void *cb_priv;
--- a/vp9/decoder/vp9_decodeframe.c
+++ b/vp9/decoder/vp9_decodeframe.c
@@ -921,7 +921,8 @@
int mi_row, mi_col;
TileData *tile_data = NULL;
- if (cm->lf.filter_level && pbi->lf_worker.data1 == NULL) {
+ if (cm->lf.filter_level && !cm->skip_loop_filter &&
+ pbi->lf_worker.data1 == NULL) {
CHECK_MEM_ERROR(cm, pbi->lf_worker.data1,
vpx_memalign(32, sizeof(LFWorkerData)));
pbi->lf_worker.hook = (VP9WorkerHook)vp9_loop_filter_worker;
@@ -931,7 +932,7 @@
}
}
- if (cm->lf.filter_level) {
+ if (cm->lf.filter_level && !cm->skip_loop_filter) {
LFWorkerData *const lf_data = (LFWorkerData*)pbi->lf_worker.data1;
// Be sure to sync as we might be resuming after a failed frame decode.
winterface->sync(&pbi->lf_worker);
@@ -1004,7 +1005,7 @@
"Failed to decode tile data");
}
// Loopfilter one row.
- if (cm->lf.filter_level) {
+ if (cm->lf.filter_level && !cm->skip_loop_filter) {
const int lf_start = mi_row - MI_BLOCK_SIZE;
LFWorkerData *const lf_data = (LFWorkerData*)pbi->lf_worker.data1;
@@ -1033,7 +1034,7 @@
}
// Loopfilter remaining rows in the frame.
- if (cm->lf.filter_level) {
+ if (cm->lf.filter_level && !cm->skip_loop_filter) {
LFWorkerData *const lf_data = (LFWorkerData*)pbi->lf_worker.data1;
winterface->sync(&pbi->lf_worker);
lf_data->start = lf_data->stop;
@@ -1657,7 +1658,7 @@
vpx_internal_error(&cm->error, VPX_CODEC_CORRUPT_FRAME,
"Decode failed. Frame data header is corrupted.");
- if (cm->lf.filter_level) {
+ if (cm->lf.filter_level && !cm->skip_loop_filter) {
vp9_loop_filter_frame_init(cm, cm->lf.filter_level);
}
@@ -1683,11 +1684,13 @@
// Multi-threaded tile decoder
*p_data_end = decode_tiles_mt(pbi, data + first_partition_size, data_end);
if (!xd->corrupted) {
- // If multiple threads are used to decode tiles, then we use those threads
- // to do parallel loopfiltering.
- vp9_loop_filter_frame_mt(new_fb, cm, pbi->mb.plane, cm->lf.filter_level,
- 0, 0, pbi->tile_workers, pbi->num_tile_workers,
- &pbi->lf_row_sync);
+ if (!cm->skip_loop_filter) {
+ // If multiple threads are used to decode tiles, then we use those
+ // threads to do parallel loopfiltering.
+ vp9_loop_filter_frame_mt(new_fb, cm, pbi->mb.plane,
+ cm->lf.filter_level, 0, 0, pbi->tile_workers,
+ pbi->num_tile_workers, &pbi->lf_row_sync);
+ }
} else {
vpx_internal_error(&cm->error, VPX_CODEC_CORRUPT_FRAME,
"Decode failed. Frame data is corrupted.");
--- a/vp9/vp9_dx_iface.c
+++ b/vp9/vp9_dx_iface.c
@@ -55,6 +55,7 @@
int invert_tile_order;
int last_show_frame; // Index of last output frame.
int byte_alignment;
+ int skip_loop_filter;
// Frame parallel related.
int frame_parallel_decode; // frame-based threading.
@@ -285,6 +286,7 @@
cm->new_fb_idx = INVALID_IDX;
cm->byte_alignment = ctx->byte_alignment;
+ cm->skip_loop_filter = ctx->skip_loop_filter;
if (ctx->get_ext_fb_cb != NULL && ctx->release_ext_fb_cb != NULL) {
pool->get_fb_cb = ctx->get_ext_fb_cb;
@@ -1059,6 +1061,19 @@
return VPX_CODEC_OK;
}
+static vpx_codec_err_t ctrl_set_skip_loop_filter(vpx_codec_alg_priv_t *ctx,
+ va_list args) {
+ ctx->skip_loop_filter = va_arg(args, int);
+
+ if (ctx->frame_workers) {
+ VP9Worker *const worker = ctx->frame_workers;
+ FrameWorkerData *const frame_worker_data = (FrameWorkerData *)worker->data1;
+ frame_worker_data->pbi->common.skip_loop_filter = ctx->skip_loop_filter;
+ }
+
+ return VPX_CODEC_OK;
+}
+
static vpx_codec_ctrl_fn_map_t decoder_ctrl_maps[] = {
{VP8_COPY_REFERENCE, ctrl_copy_reference},
@@ -1072,6 +1087,7 @@
{VP9_INVERT_TILE_DECODE_ORDER, ctrl_set_invert_tile_order},
{VPXD_SET_DECRYPTOR, ctrl_set_decryptor},
{VP9_SET_BYTE_ALIGNMENT, ctrl_set_byte_alignment},
+ {VP9_SET_SKIP_LOOP_FILTER, ctrl_set_skip_loop_filter},
// Getters
{VP8D_GET_LAST_REF_UPDATES, ctrl_get_last_ref_updates},
--- a/vpx/vp8dx.h
+++ b/vpx/vp8dx.h
@@ -106,6 +106,13 @@
*/
VP9_INVERT_TILE_DECODE_ORDER,
+ /** control function to set the skip loop filter flag. Valid values are
+ * integers. The decoder will skip the loop filter when its value is set to
+ * nonzero. If the loop filter is skipped the decoder may accumulate decode
+ * artifacts. The default value is 0.
+ */
+ VP9_SET_SKIP_LOOP_FILTER,
+
VP8_DECODER_CTRL_ID_MAX
};