--- /dev/null
+/*
+ * 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
#include "third_party/googletest/src/include/gtest/gtest.h"
namespace libvpx_test {
-void Encoder::EncodeFrame(VideoSource *video, unsigned long flags) {
+void Encoder::EncodeFrame(VideoSource *video, const unsigned long frame_flags) {
if (video->img())
- EncodeFrameInternal(*video, flags);
+ EncodeFrameInternal(*video, frame_flags);
else
Flush();
}
void Encoder::EncodeFrameInternal(const VideoSource &video,
- unsigned long flags) {
+ const unsigned long frame_flags) {
vpx_codec_err_t res;
const vpx_image_t *img = video.img();
cfg_.g_h = img->d_h;
cfg_.g_timebase = video.timebase();
cfg_.rc_twopass_stats_in = stats_->buf();
- res = vpx_codec_enc_init(&encoder_, &vpx_codec_vp8_cx_algo, &cfg_, 0);
+ res = vpx_codec_enc_init(&encoder_, &vpx_codec_vp8_cx_algo, &cfg_,
+ init_flags_);
ASSERT_EQ(VPX_CODEC_OK, res) << EncoderError();
}
// Encode the frame
res = vpx_codec_encode(&encoder_,
video.img(), video.pts(), video.duration(),
- flags, deadline_);
+ frame_flags, deadline_);
ASSERT_EQ(VPX_CODEC_OK, res) << EncoderError();
}
cfg_.g_pass = VPX_RC_LAST_PASS;
BeginPassHook(pass);
- Encoder encoder(cfg_, deadline_, &stats_);
+ Encoder encoder(cfg_, deadline_, init_flags_, &stats_);
#if CONFIG_VP8_DECODER
Decoder decoder(dec_cfg);
bool has_cxdata = false;
PreEncodeFrameHook(video);
PreEncodeFrameHook(video, &encoder);
- encoder.EncodeFrame(video, flags_);
+ encoder.EncodeFrame(video, frame_flags_);
CxDataIterator iter = encoder.GetCxData();
while (const vpx_codec_cx_pkt_t *pkt = iter.Next()) {
again = true;
- if (pkt->kind != VPX_CODEC_CX_FRAME_PKT)
- continue;
+ switch (pkt->kind) {
+ case VPX_CODEC_CX_FRAME_PKT:
#if CONFIG_VP8_DECODER
- has_cxdata = true;
- decoder.DecodeFrame((const uint8_t*)pkt->data.frame.buf,
- pkt->data.frame.sz);
+ has_cxdata = true;
+ decoder.DecodeFrame((const uint8_t*)pkt->data.frame.buf,
+ pkt->data.frame.sz);
#endif
- ASSERT_GE(pkt->data.frame.pts, last_pts_);
- last_pts_ = pkt->data.frame.pts;
- FramePktHook(pkt);
+ ASSERT_GE(pkt->data.frame.pts, last_pts_);
+ last_pts_ = pkt->data.frame.pts;
+ FramePktHook(pkt);
+ break;
+
+ case VPX_CODEC_PSNR_PKT:
+ PSNRPktHook(pkt);
+ break;
+
+ default:
+ break;
+ }
}
#if CONFIG_VP8_DECODER
class Encoder {
public:
Encoder(vpx_codec_enc_cfg_t cfg, unsigned long deadline,
- TwopassStatsStore *stats)
- : cfg_(cfg), deadline_(deadline), stats_(stats) {
+ const unsigned long init_flags, TwopassStatsStore *stats)
+ : cfg_(cfg), deadline_(deadline), init_flags_(init_flags), stats_(stats) {
memset(&encoder_, 0, sizeof(encoder_));
}
}
// This is a thin wrapper around vpx_codec_encode(), so refer to
// vpx_encoder.h for its semantics.
- void EncodeFrame(VideoSource *video, unsigned long flags);
+ void EncodeFrame(VideoSource *video, const unsigned long frame_flags);
// Convenience wrapper for EncodeFrame()
void EncodeFrame(VideoSource *video) {
}
// Encode an image
- void EncodeFrameInternal(const VideoSource &video, unsigned long flags);
+ void EncodeFrameInternal(const VideoSource &video,
+ const unsigned long frame_flags);
// Flush the encoder on EOS
void Flush();
vpx_codec_ctx_t encoder_;
vpx_codec_enc_cfg_t cfg_;
unsigned long deadline_;
+ unsigned long init_flags_;
TwopassStatsStore *stats_;
};
// classes directly, so that tests can be parameterized differently.
class EncoderTest {
protected:
- EncoderTest() : abort_(false), flags_(0), last_pts_(0) {}
+ EncoderTest() : abort_(false), init_flags_(0), frame_flags_(0),
+ last_pts_(0) {}
virtual ~EncoderTest() {}
// Hook to be called on every compressed data packet.
virtual void FramePktHook(const vpx_codec_cx_pkt_t *pkt) {}
+ // Hook to be called on every PSNR packet.
+ virtual void PSNRPktHook(const vpx_codec_cx_pkt_t *pkt) {}
+
// Hook to determine whether the encode loop should continue.
virtual bool Continue() const { return !abort_; }
unsigned int passes_;
unsigned long deadline_;
TwopassStatsStore stats_;
- unsigned long flags_;
+ unsigned long init_flags_;
+ unsigned long frame_flags_;
vpx_codec_pts_t last_pts_;
};
--- /dev/null
+/*
+ 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/encode_test_driver.h"
+#include "test/i420_video_source.h"
+
+namespace {
+
+class ErrorResilienceTest : public libvpx_test::EncoderTest,
+ public ::testing::TestWithParam<int> {
+ protected:
+ ErrorResilienceTest() {
+ psnr_ = 0.0;
+ nframes_ = 0;
+ encoding_mode_ = static_cast<libvpx_test::TestMode>(GetParam());
+ }
+ virtual ~ErrorResilienceTest() {}
+
+ virtual void SetUp() {
+ InitializeConfig();
+ SetMode(encoding_mode_);
+ }
+
+ virtual void BeginPassHook(unsigned int /*pass*/) {
+ psnr_ = 0.0;
+ nframes_ = 0;
+ }
+
+ virtual bool Continue() const {
+ return !HasFatalFailure() && !abort_;
+ }
+
+ virtual void PSNRPktHook(const vpx_codec_cx_pkt_t *pkt) {
+ psnr_ += pkt->data.psnr.psnr[0];
+ nframes_++;
+ }
+
+ double GetAveragePsnr() const {
+ if (nframes_)
+ return psnr_ / nframes_;
+ return 0.0;
+ }
+
+ private:
+ double psnr_;
+ unsigned int nframes_;
+ libvpx_test::TestMode encoding_mode_;
+};
+
+TEST_P(ErrorResilienceTest, OnVersusOff) {
+ const vpx_rational timebase = { 33333333, 1000000000 };
+ cfg_.g_timebase = timebase;
+ cfg_.rc_target_bitrate = 2000;
+ cfg_.g_lag_in_frames = 25;
+
+ init_flags_ = VPX_CODEC_USE_PSNR;
+
+ libvpx_test::I420VideoSource video("hantro_collage_w352h288.yuv", 352, 288,
+ timebase.den, timebase.num, 0, 30);
+
+ // Error resilient mode OFF.
+ cfg_.g_error_resilient = 0;
+ ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+ const double psnr_resilience_off = GetAveragePsnr();
+ EXPECT_GT(psnr_resilience_off, 25.0);
+
+ // Error resilient mode ON.
+ cfg_.g_error_resilient = 1;
+ ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+ const double psnr_resilience_on = GetAveragePsnr();
+ EXPECT_GT(psnr_resilience_on, 25.0);
+
+ // Test that turning on error resilient mode hurts by 10% at most.
+ if (psnr_resilience_off > 0.0) {
+ const double psnr_ratio = psnr_resilience_on / psnr_resilience_off;
+ EXPECT_GE(psnr_ratio, 0.9);
+ EXPECT_LE(psnr_ratio, 1.1);
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(OnOffTest, ErrorResilienceTest,
+ ONE_PASS_TEST_MODES);
+} // namespace
virtual void PreEncodeFrameHook(::libvpx_test::VideoSource *video,
::libvpx_test::Encoder *encoder) {
if (kf_do_force_kf_)
- flags_ = (video->frame() % 3) ? 0 : VPX_EFLAG_FORCE_KF;
+ frame_flags_ = (video->frame() % 3) ? 0 : VPX_EFLAG_FORCE_KF;
if (set_cpu_used_ && video->frame() == 1)
encoder->Control(VP8E_SET_CPUUSED, set_cpu_used_);
}
##
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
LIBVPX_TEST_SRCS-$(CONFIG_VP8_ENCODER) += i420_video_source.h
LIBVPX_TEST_SRCS-$(CONFIG_VP8_ENCODER) += keyframe_test.cc
LIBVPX_TEST_SRCS-$(CONFIG_VP8_ENCODER) += resize_test.cc