Merge "Add control to skip loop filter in VP9 decoder."
This commit is contained in:
Коммит
8c854769fc
|
@ -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.
|
||||
|
|
|
@ -66,6 +66,7 @@ LIBVPX_TEST_SRCS-$(CONFIG_DECODERS) += ../tools_common.h
|
|||
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
|
||||
|
|
|
@ -0,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
|
|
@ -264,6 +264,7 @@ typedef struct VP9Common {
|
|||
|
||||
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;
|
||||
|
|
|
@ -921,7 +921,8 @@ static const uint8_t *decode_tiles(VP9Decoder *pbi,
|
|||
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 @@ static const uint8_t *decode_tiles(VP9Decoder *pbi,
|
|||
}
|
||||
}
|
||||
|
||||
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 @@ static const uint8_t *decode_tiles(VP9Decoder *pbi,
|
|||
"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 @@ static const uint8_t *decode_tiles(VP9Decoder *pbi,
|
|||
}
|
||||
|
||||
// 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 @@ void vp9_decode_frame(VP9Decoder *pbi,
|
|||
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 @@ void vp9_decode_frame(VP9Decoder *pbi,
|
|||
// 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.");
|
||||
|
|
|
@ -55,6 +55,7 @@ struct vpx_codec_alg_priv {
|
|||
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 @@ static void init_buffer_callbacks(vpx_codec_alg_priv_t *ctx) {
|
|||
|
||||
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 @@ static vpx_codec_err_t ctrl_set_byte_alignment(vpx_codec_alg_priv_t *ctx,
|
|||
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 @@ static vpx_codec_ctrl_fn_map_t decoder_ctrl_maps[] = {
|
|||
{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},
|
||||
|
|
|
@ -106,6 +106,13 @@ enum vp8_dec_control_id {
|
|||
*/
|
||||
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
|
||||
};
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче