ref: 8c854769fcf082c3c3fd42f0b5bfb4e4aa6d7b8c
parent: 44138d7d9e1324a29581e54061c7b3304c1b5391
parent: bfb6d488122a42b6c86e4af2f1ecd524fa1864e2
author: Frank Galligan <[email protected]>
date: Fri Jun 5 14:02:08 EDT 2015
Merge "Add control to skip loop filter in VP9 decoder."
--- 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
};