Merge "Unit Test for Error Resilience Mode"
diff --git a/test/cq_test.cc b/test/cq_test.cc
new file mode 100644
index 0000000..42ee2a2
--- /dev/null
+++ b/test/cq_test.cc
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2012 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 <cmath>
+#include "third_party/googletest/src/include/gtest/gtest.h"
+#include "test/encode_test_driver.h"
+#include "test/i420_video_source.h"
+
+// CQ level range: [kCQLevelMin, kCQLevelMax).
+const int kCQLevelMin = 4;
+const int kCQLevelMax = 63;
+const int kCQLevelStep = 8;
+const int kCQTargetBitrate = 2000;
+
+namespace {
+
+class CQTest : public libvpx_test::EncoderTest,
+ public ::testing::TestWithParam<int> {
+ protected:
+ CQTest() : cq_level_(GetParam()) { init_flags_ = VPX_CODEC_USE_PSNR; }
+ virtual ~CQTest() {}
+
+ virtual void SetUp() {
+ InitializeConfig();
+ SetMode(libvpx_test::kTwoPassGood);
+ }
+
+ virtual void BeginPassHook(unsigned int /*pass*/) {
+ file_size_ = 0;
+ psnr_ = 0.0;
+ n_frames_ = 0;
+ }
+
+ virtual bool Continue() const {
+ return !HasFatalFailure() && !abort_;
+ }
+
+ virtual void PreEncodeFrameHook(libvpx_test::VideoSource *video,
+ libvpx_test::Encoder *encoder) {
+ if (video->frame() == 1) {
+ if (cfg_.rc_end_usage == VPX_CQ) {
+ encoder->Control(VP8E_SET_CQ_LEVEL, cq_level_);
+ }
+ encoder->Control(VP8E_SET_CPUUSED, 3);
+ }
+ }
+
+ virtual void PSNRPktHook(const vpx_codec_cx_pkt_t *pkt) {
+ psnr_ += pow(10.0, pkt->data.psnr.psnr[0] / 10.0);
+ n_frames_++;
+ }
+
+ virtual void FramePktHook(const vpx_codec_cx_pkt_t *pkt) {
+ file_size_ += pkt->data.frame.sz;
+ }
+
+ double GetLinearPSNROverBitrate() const {
+ double avg_psnr = log10(psnr_ / n_frames_) * 10.0;
+ return pow(10.0, avg_psnr / 10.0) / file_size_;
+ }
+
+ int file_size() const { return file_size_; }
+ int n_frames() const { return n_frames_; }
+
+ private:
+ int cq_level_;
+ int file_size_;
+ double psnr_;
+ int n_frames_;
+};
+
+int prev_actual_bitrate = kCQTargetBitrate;
+TEST_P(CQTest, LinearPSNRIsHigherForCQLevel) {
+ const vpx_rational timebase = { 33333333, 1000000000 };
+ cfg_.g_timebase = timebase;
+ cfg_.rc_target_bitrate = kCQTargetBitrate;
+ cfg_.g_lag_in_frames = 25;
+
+ cfg_.rc_end_usage = VPX_CQ;
+ libvpx_test::I420VideoSource video("hantro_collage_w352h288.yuv", 352, 288,
+ timebase.den, timebase.num, 0, 30);
+ ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+ const double cq_psnr_lin = GetLinearPSNROverBitrate();
+ const int cq_actual_bitrate = file_size() * 8 * 30 / (n_frames() * 1000);
+ EXPECT_LE(cq_actual_bitrate, kCQTargetBitrate);
+ EXPECT_LE(cq_actual_bitrate, prev_actual_bitrate);
+ prev_actual_bitrate = cq_actual_bitrate;
+
+ // try targeting the approximate same bitrate with VBR mode
+ cfg_.rc_end_usage = VPX_VBR;
+ cfg_.rc_target_bitrate = cq_actual_bitrate;
+ ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+ const double vbr_psnr_lin = GetLinearPSNROverBitrate();
+ EXPECT_GE(cq_psnr_lin, vbr_psnr_lin);
+}
+
+INSTANTIATE_TEST_CASE_P(CQLevelRange, CQTest,
+ ::testing::Range(kCQLevelMin, kCQLevelMax,
+ kCQLevelStep));
+} // namespace
diff --git a/test/keyframe_test.cc b/test/keyframe_test.cc
index b003aa3..d0c81df 100644
--- a/test/keyframe_test.cc
+++ b/test/keyframe_test.cc
@@ -62,7 +62,10 @@
::libvpx_test::RandomVideoSource video;
ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
- EXPECT_GT(kf_count_, 1);
+ // In realtime mode - auto placed keyframes are exceedingly rare, don't
+ // bother with this check if(GetParam() > 0)
+ if(GetParam() > 0)
+ EXPECT_GT(kf_count_, 1);
}
TEST_P(KeyframeTest, TestDisableKeyframes) {
@@ -121,7 +124,10 @@
ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
- EXPECT_EQ(2u, kf_pts_list_.size()) << " Not the right number of keyframes ";
+ // In realtime mode - auto placed keyframes are exceedingly rare, don't
+ // bother with this check
+ if(GetParam() > 0)
+ EXPECT_EQ(2u, kf_pts_list_.size()) << " Not the right number of keyframes ";
// Verify that keyframes match the file keyframes in the file.
for (std::vector<vpx_codec_pts_t>::const_iterator iter = kf_pts_list_.begin();
diff --git a/test/subtract_test.cc b/test/subtract_test.cc
new file mode 100644
index 0000000..99363de
--- /dev/null
+++ b/test/subtract_test.cc
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2012 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 "third_party/googletest/src/include/gtest/gtest.h"
+#include "test/acm_random.h"
+extern "C" {
+#include "vpx_config.h"
+#include "vpx_rtcd.h"
+#include "vp8/common/blockd.h"
+#include "vp8/encoder/block.h"
+#include "vpx_mem/vpx_mem.h"
+}
+
+typedef void (*subtract_b_fn_t)(BLOCK *be, BLOCKD *bd, int pitch);
+
+namespace {
+
+class SubtractBlockTest : public ::testing::TestWithParam<subtract_b_fn_t> {};
+
+using libvpx_test::ACMRandom;
+
+TEST_P(SubtractBlockTest, SimpleSubtract) {
+ ACMRandom rnd(ACMRandom::DeterministicSeed());
+ BLOCK be;
+ BLOCKD bd;
+ // in libvpx, this stride is always 16
+ const int kDiffPredStride = 16;
+ const int kSrcStride[] = {32, 16, 8, 4, 0};
+ const int kBlockWidth = 4;
+ const int kBlockHeight = 4;
+
+ // Allocate... align to 16 for mmx/sse tests
+ uint8_t *source = reinterpret_cast<uint8_t*>(
+ vpx_memalign(16, kBlockHeight * kSrcStride[0] * sizeof(*source)));
+ be.src_diff = reinterpret_cast<int16_t*>(
+ vpx_memalign(16, kBlockHeight * kDiffPredStride * sizeof(*be.src_diff)));
+ bd.predictor = reinterpret_cast<unsigned char*>(
+ vpx_memalign(16, kBlockHeight * kDiffPredStride * sizeof(*bd.predictor)));
+
+ for(int i = 0; kSrcStride[i] > 0; ++i) {
+ // start at block0
+ be.src = 0;
+ be.base_src = &source;
+ be.src_stride = kSrcStride[i];
+
+ // set difference
+ int16_t *src_diff = be.src_diff;
+ for (int r = 0; r < kBlockHeight; ++r) {
+ for (int c = 0; c < kBlockWidth; ++c) {
+ src_diff[c] = 0xa5a5;
+ }
+ src_diff += kDiffPredStride;
+ }
+
+ // set destination
+ uint8_t *base_src = *be.base_src;
+ for (int r = 0; r < kBlockHeight; ++r) {
+ for (int c = 0; c < kBlockWidth; ++c) {
+ base_src[c] = rnd.Rand8();
+ }
+ base_src += be.src_stride;
+ }
+
+ // set predictor
+ uint8_t *predictor = bd.predictor;
+ for (int r = 0; r < kBlockHeight; ++r) {
+ for (int c = 0; c < kBlockWidth; ++c) {
+ predictor[c] = rnd.Rand8();
+ }
+ predictor += kDiffPredStride;
+ }
+
+ GetParam()(&be, &bd, kDiffPredStride);
+
+ base_src = *be.base_src;
+ src_diff = be.src_diff;
+ predictor = bd.predictor;
+ for (int r = 0; r < kBlockHeight; ++r) {
+ for (int c = 0; c < kBlockWidth; ++c) {
+ EXPECT_EQ(base_src[c], (src_diff[c] + predictor[c])) << "r = " << r
+ << ", c = " << c;
+ }
+ src_diff += kDiffPredStride;
+ predictor += kDiffPredStride;
+ base_src += be.src_stride;
+ }
+ }
+ vpx_free(be.src_diff);
+ vpx_free(source);
+ vpx_free(bd.predictor);
+}
+
+INSTANTIATE_TEST_CASE_P(C, SubtractBlockTest,
+ ::testing::Values(vp8_subtract_b_c));
+
+#if HAVE_MMX
+INSTANTIATE_TEST_CASE_P(MMX, SubtractBlockTest,
+ ::testing::Values(vp8_subtract_b_mmx));
+#endif
+
+#if HAVE_SSE2
+INSTANTIATE_TEST_CASE_P(SSE2, SubtractBlockTest,
+ ::testing::Values(vp8_subtract_b_sse2));
+#endif
+
+} // namespace
diff --git a/test/test.mk b/test/test.mk
index bf49bd2..7e07320 100644
--- a/test/test.mk
+++ b/test/test.mk
@@ -10,6 +10,7 @@
##
LIBVPX_TEST_SRCS-$(CONFIG_VP8_ENCODER) += altref_test.cc
LIBVPX_TEST_SRCS-$(CONFIG_VP8_ENCODER) += config_test.cc
+LIBVPX_TEST_SRCS-$(CONFIG_VP8_ENCODER) += cq_test.cc
LIBVPX_TEST_SRCS-$(CONFIG_VP8_ENCODER) += encode_test_driver.cc
LIBVPX_TEST_SRCS-$(CONFIG_VP8_ENCODER) += encode_test_driver.h
LIBVPX_TEST_SRCS-$(CONFIG_VP8_ENCODER) += error_resilience_test.cc
@@ -40,6 +41,7 @@
LIBVPX_TEST_SRCS-yes += sad_test.cc
LIBVPX_TEST_SRCS-$(CONFIG_VP8_ENCODER) += set_roi.cc
LIBVPX_TEST_SRCS-yes += sixtap_predict_test.cc
+LIBVPX_TEST_SRCS-$(CONFIG_VP8_ENCODER) += subtract_test.cc
endif
diff --git a/vp8/common/rtcd.c b/vp8/common/rtcd.c
index 3150fff..01dad46 100644
--- a/vp8/common/rtcd.c
+++ b/vp8/common/rtcd.c
@@ -13,17 +13,43 @@
#if CONFIG_MULTITHREAD && defined(_WIN32)
#include <windows.h>
+#include <stdlib.h>
static void once(void (*func)(void))
{
- /* Using a static initializer here rather than InitializeCriticalSection()
- * since there's no race-free context in which to execute it. Protecting
- * it with an atomic op like InterlockedCompareExchangePointer introduces
- * an x86 dependency, and InitOnceExecuteOnce requires Vista.
- */
- static CRITICAL_SECTION lock = {(void *)-1, -1, 0, 0, 0, 0};
+ static CRITICAL_SECTION *lock;
+ static LONG waiters;
static int done;
+ void *lock_ptr = &lock;
- EnterCriticalSection(&lock);
+ /* If the initialization is complete, return early. This isn't just an
+ * optimization, it prevents races on the destruction of the global
+ * lock.
+ */
+ if(done)
+ return;
+
+ InterlockedIncrement(&waiters);
+
+ /* Get a lock. We create one and try to make it the one-true-lock,
+ * throwing it away if we lost the race.
+ */
+
+ {
+ /* Scope to protect access to new_lock */
+ CRITICAL_SECTION *new_lock = malloc(sizeof(CRITICAL_SECTION));
+ InitializeCriticalSection(new_lock);
+ if (InterlockedCompareExchangePointer(lock_ptr, new_lock, NULL) != NULL)
+ {
+ DeleteCriticalSection(new_lock);
+ free(new_lock);
+ }
+ }
+
+ /* At this point, we have a lock that can be synchronized on. We don't
+ * care which thread actually performed the allocation.
+ */
+
+ EnterCriticalSection(lock);
if (!done)
{
@@ -31,7 +57,17 @@
done = 1;
}
- LeaveCriticalSection(&lock);
+ LeaveCriticalSection(lock);
+
+ /* Last one out should free resources. The destructed objects are
+ * protected by checking if(done) above.
+ */
+ if(!InterlockedDecrement(&waiters))
+ {
+ DeleteCriticalSection(lock);
+ free(lock);
+ lock = NULL;
+ }
}
diff --git a/vp8/encoder/onyx_if.c b/vp8/encoder/onyx_if.c
index b4e02e2..3ed36d3 100644
--- a/vp8/encoder/onyx_if.c
+++ b/vp8/encoder/onyx_if.c
@@ -3970,18 +3970,12 @@
/* Test to see if the stats generated for this frame indicate that
* we should have coded a key frame (assuming that we didn't)!
*/
- if (cpi->pass != 2 && cpi->oxcf.auto_key && cm->frame_type != KEY_FRAME)
- {
- int key_frame_decision = decide_key_frame(cpi);
- if (cpi->compressor_speed == 2)
- {
- /* we don't do re-encoding in realtime mode
- * if key frame is decided then we force it on next frame */
- cpi->force_next_frame_intra = key_frame_decision;
- }
+ if (cpi->pass != 2 && cpi->oxcf.auto_key && cm->frame_type != KEY_FRAME
+ && cpi->compressor_speed != 2)
+ {
#if !(CONFIG_REALTIME_ONLY)
- else if (key_frame_decision)
+ if (decide_key_frame(cpi))
{
/* Reset all our sizing numbers and recode */
cm->frame_type = KEY_FRAME;
diff --git a/vp8/encoder/pickinter.c b/vp8/encoder/pickinter.c
index 3cad8bf..6050d15 100644
--- a/vp8/encoder/pickinter.c
+++ b/vp8/encoder/pickinter.c
@@ -865,7 +865,8 @@
if (cpi->oxcf.mr_encoder_id && !cpi->mr_low_res_mv_avail)
cpi->sf.improved_mv_pred = 0;
- if (cpi->oxcf.mr_encoder_id && cpi->mr_low_res_mv_avail)
+ if (cpi->oxcf.mr_encoder_id && cpi->mr_low_res_mv_avail
+ && parent_ref_frame)
{
/* Use parent MV as predictor. Adjust search range
* accordingly.
@@ -911,6 +912,7 @@
#if CONFIG_MULTI_RES_ENCODING
if (cpi->oxcf.mr_encoder_id && cpi->mr_low_res_mv_avail &&
dissim <= 2 &&
+ parent_ref_frame &&
MAX(abs(best_ref_mv.as_mv.row - parent_ref_mv.as_mv.row),
abs(best_ref_mv.as_mv.col - parent_ref_mv.as_mv.col)) <= 4)
{