Add initial keyframe tests

Implements a couple simple tests of the encoder API using the gtest
framework:

  TestDisableKeyframes
  TestForceKeyframe
  TestKeyframeMaxDistance

Change-Id: I38e93fe242fbeb30bb11b23ac12de8ddc291a28d
This commit is contained in:
John Koleszar 2012-05-16 15:27:00 -07:00
parent 2d225689d3
commit b9180fc049
5 changed files with 531 additions and 0 deletions

137
test/encode_test_driver.cc Normal file
View File

@ -0,0 +1,137 @@
/*
* 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 "test/encode_test_driver.h"
#include "test/video_source.h"
#include "third_party/googletest/src/include/gtest/gtest.h"
namespace libvpx_test {
void Encoder::EncodeFrame(VideoSource *video, unsigned long flags) {
if (video->img())
EncodeFrameInternal(*video, flags);
else
Flush();
// Handle twopass stats
CxDataIterator iter = GetCxData();
while (const vpx_codec_cx_pkt_t *pkt = iter.Next()) {
if (pkt->kind != VPX_CODEC_STATS_PKT)
continue;
stats_->Append(*pkt);
}
}
void Encoder::EncodeFrameInternal(const VideoSource &video,
unsigned long flags) {
vpx_codec_err_t res;
const vpx_image_t *img = video.img();
// Handle first frame initialization
if (!encoder_.priv) {
cfg_.g_w = img->d_w;
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);
ASSERT_EQ(VPX_CODEC_OK, res) << EncoderError();
}
// Handle frame resizing
if (cfg_.g_w != img->d_w || cfg_.g_h != img->d_h) {
cfg_.g_w = img->d_w;
cfg_.g_h = img->d_h;
res = vpx_codec_enc_config_set(&encoder_, &cfg_);
ASSERT_EQ(VPX_CODEC_OK, res) << EncoderError();
}
// Encode the frame
res = vpx_codec_encode(&encoder_,
video.img(), video.pts(), video.duration(),
flags, deadline_);
ASSERT_EQ(VPX_CODEC_OK, res) << EncoderError();
}
void Encoder::Flush() {
const vpx_codec_err_t res = vpx_codec_encode(&encoder_, NULL, 0, 0, 0,
deadline_);
ASSERT_EQ(VPX_CODEC_OK, res) << EncoderError();
}
void EncoderTest::SetMode(TestMode mode) {
switch (mode) {
case kRealTime:
deadline_ = VPX_DL_REALTIME;
break;
case kOnePassGood:
case kTwoPassGood:
deadline_ = VPX_DL_GOOD_QUALITY;
break;
case kOnePassBest:
case kTwoPassBest:
deadline_ = VPX_DL_BEST_QUALITY;
break;
default:
ASSERT_TRUE(false) << "Unexpected mode " << mode;
}
if (mode == kTwoPassGood || mode == kTwoPassBest)
passes_ = 2;
else
passes_ = 1;
}
void EncoderTest::RunLoop(VideoSource *video) {
for (unsigned int pass = 0; pass < passes_; pass++) {
if (passes_ == 1)
cfg_.g_pass = VPX_RC_ONE_PASS;
else if (pass == 0)
cfg_.g_pass = VPX_RC_FIRST_PASS;
else
cfg_.g_pass = VPX_RC_LAST_PASS;
BeginPassHook(pass);
Encoder encoder(cfg_, deadline_, &stats_);
bool again;
for (video->Begin(), again = true; again; video->Next()) {
again = video->img() != NULL;
PreEncodeFrameHook(video);
encoder.EncodeFrame(video, 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;
FramePktHook(pkt);
}
if (!Continue())
break;
}
EndPassHook();
if (!Continue())
break;
}
}
} // namespace libvpx_test

178
test/encode_test_driver.h Normal file
View File

@ -0,0 +1,178 @@
/*
* 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.
*/
#ifndef TEST_ENCODE_TEST_DRIVER_H_
#define TEST_ENCODE_TEST_DRIVER_H_
#include <string>
#include <vector>
#include "third_party/googletest/src/include/gtest/gtest.h"
#include "vpx/vpx_encoder.h"
#include "vpx/vp8cx.h"
namespace libvpx_test {
class VideoSource;
enum TestMode {
kRealTime,
kOnePassGood,
kOnePassBest,
kTwoPassGood,
kTwoPassBest
};
#define ALL_TEST_MODES ::testing::Values(::libvpx_test::kRealTime, \
::libvpx_test::kOnePassGood, \
::libvpx_test::kOnePassBest, \
::libvpx_test::kTwoPassGood, \
::libvpx_test::kTwoPassBest)
// Provides an object to handle the libvpx get_cx_data() iteration pattern
class CxDataIterator {
public:
explicit CxDataIterator(vpx_codec_ctx_t *encoder)
: encoder_(encoder), iter_(NULL) {}
const vpx_codec_cx_pkt_t *Next() {
return vpx_codec_get_cx_data(encoder_, &iter_);
}
private:
vpx_codec_ctx_t *encoder_;
vpx_codec_iter_t iter_;
};
// Implements an in-memory store for libvpx twopass statistics
class TwopassStatsStore {
public:
void Append(const vpx_codec_cx_pkt_t &pkt) {
buffer_.append(reinterpret_cast<char *>(pkt.data.twopass_stats.buf),
pkt.data.twopass_stats.sz);
}
vpx_fixed_buf_t buf() {
const vpx_fixed_buf_t buf = { &buffer_[0], buffer_.size() };
return buf;
}
protected:
std::string buffer_;
};
// Provides a simplified interface to manage one video encoding pass, given
// a configuration and video source.
//
// TODO(jkoleszar): The exact services it provides and the appropriate
// level of abstraction will be fleshed out as more tests are written.
class Encoder {
public:
Encoder(vpx_codec_enc_cfg_t cfg, unsigned long deadline,
TwopassStatsStore *stats)
: cfg_(cfg), deadline_(deadline), stats_(stats) {
memset(&encoder_, 0, sizeof(encoder_));
}
~Encoder() {
vpx_codec_destroy(&encoder_);
}
CxDataIterator GetCxData() {
return CxDataIterator(&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);
// Convenience wrapper for EncodeFrame()
void EncodeFrame(VideoSource *video) {
EncodeFrame(video, 0);
}
void set_deadline(unsigned long deadline) {
deadline_ = deadline;
}
protected:
const char *EncoderError() {
const char *detail = vpx_codec_error_detail(&encoder_);
return detail ? detail : vpx_codec_error(&encoder_);
}
// Encode an image
void EncodeFrameInternal(const VideoSource &video, unsigned long flags);
// Flush the encoder on EOS
void Flush();
vpx_codec_ctx_t encoder_;
vpx_codec_enc_cfg_t cfg_;
unsigned long deadline_;
TwopassStatsStore *stats_;
};
// Common test functionality for all Encoder tests.
//
// This class is a mixin which provides the main loop common to all
// encoder tests. It provides hooks which can be overridden by subclasses
// to implement each test's specific behavior, while centralizing the bulk
// of the boilerplate. Note that it doesn't inherit the gtest testing
// classes directly, so that tests can be parameterized differently.
class EncoderTest {
protected:
EncoderTest() : abort_(false), flags_(0) {}
virtual ~EncoderTest() {}
// Initialize the cfg_ member with the default configuration.
void InitializeConfig() {
const vpx_codec_err_t res = vpx_codec_enc_config_default(
&vpx_codec_vp8_cx_algo, &cfg_, 0);
ASSERT_EQ(VPX_CODEC_OK, res);
}
// Map the TestMode enum to the deadline_ and passes_ variables.
void SetMode(TestMode mode);
// Main loop.
virtual void RunLoop(VideoSource *video);
// Hook to be called at the beginning of a pass.
virtual void BeginPassHook(unsigned int pass) {}
// Hook to be called at the end of a pass.
virtual void EndPassHook() {}
// Hook to be called before encoding a frame.
virtual void PreEncodeFrameHook(VideoSource *video) {}
// Hook to be called on every compressed data packet.
virtual void FramePktHook(const vpx_codec_cx_pkt_t *pkt) {}
// Hook to determine whether the encode loop should continue.
virtual bool Continue() const { return !abort_; }
bool abort_;
vpx_codec_enc_cfg_t cfg_;
unsigned int passes_;
unsigned long deadline_;
TwopassStatsStore stats_;
unsigned long flags_;
};
} // namespace libvpx_test
// Macros to be used with ::testing::Combine
#define PARAMS(...) ::testing::TestWithParam< std::tr1::tuple< __VA_ARGS__ > >
#define GET_PARAM(k) std::tr1::get< k >(GetParam())
#endif // TEST_ENCODE_TEST_DRIVER_H_

103
test/keyframe_test.cc Normal file
View File

@ -0,0 +1,103 @@
/*
* 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 <climits>
#include <vector>
#include "test/encode_test_driver.h"
#include "test/video_source.h"
#include "third_party/googletest/src/include/gtest/gtest.h"
namespace {
class KeyframeTest : public ::libvpx_test::EncoderTest,
public ::testing::TestWithParam<enum libvpx_test::TestMode> {
protected:
virtual void SetUp() {
InitializeConfig();
SetMode(GetParam());
kf_count_ = 0;
kf_count_max_ = INT_MAX;
kf_do_force_kf_ = false;
}
virtual bool Continue() {
return !HasFatalFailure() && !abort_;
}
virtual void PreEncodeFrameHook(::libvpx_test::VideoSource *video) {
if (kf_do_force_kf_)
flags_ = (video->frame() % 3) ? 0 : VPX_EFLAG_FORCE_KF;
}
virtual void FramePktHook(const vpx_codec_cx_pkt_t *pkt) {
if (pkt->data.frame.flags & VPX_FRAME_IS_KEY) {
kf_pts_list_.push_back(pkt->data.frame.pts);
kf_count_++;
abort_ |= kf_count_ > kf_count_max_;
}
}
bool kf_do_force_kf_;
int kf_count_;
int kf_count_max_;
std::vector< vpx_codec_pts_t > kf_pts_list_;
};
TEST_P(KeyframeTest, TestRandomVideoSource) {
// Validate that encoding the RandomVideoSource produces multiple keyframes.
// This validates the results of the TestDisableKeyframes test.
kf_count_max_ = 2; // early exit successful tests.
::libvpx_test::RandomVideoSource video;
ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
EXPECT_GT(kf_count_, 1);
}
TEST_P(KeyframeTest, TestDisableKeyframes) {
cfg_.kf_mode = VPX_KF_DISABLED;
kf_count_max_ = 1; // early exit failed tests.
::libvpx_test::RandomVideoSource video;
ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
EXPECT_EQ(1, kf_count_);
}
TEST_P(KeyframeTest, TestForceKeyframe) {
cfg_.kf_mode = VPX_KF_DISABLED;
kf_do_force_kf_ = true;
::libvpx_test::DummyVideoSource video;
ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
// verify that every third frame is a keyframe.
for (std::vector<vpx_codec_pts_t>::iterator iter = kf_pts_list_.begin();
iter != kf_pts_list_.end();
++iter) {
ASSERT_EQ(0, *iter % 3) << "Unexpected keyframe at frame " << *iter;
}
}
TEST_P(KeyframeTest, TestKeyframeMaxDistance) {
cfg_.kf_max_dist = 25;
::libvpx_test::DummyVideoSource video;
ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
// verify that keyframe interval matches kf_max_dist
for (std::vector<vpx_codec_pts_t>::iterator iter = kf_pts_list_.begin();
iter != kf_pts_list_.end();
iter++) {
ASSERT_EQ(0, *iter % 25) << "Unexpected keyframe at frame " << *iter;
}
}
INSTANTIATE_TEST_CASE_P(AllModes, KeyframeTest, ALL_TEST_MODES);
} // namespace

View File

@ -1,4 +1,8 @@
LIBVPX_TEST_SRCS-yes += test.mk
LIBVPX_TEST_SRCS-yes += boolcoder_test.cc
LIBVPX_TEST_SRCS-yes += encode_test_driver.cc
LIBVPX_TEST_SRCS-yes += encode_test_driver.h
LIBVPX_TEST_SRCS-yes += idctllm_test.cc
LIBVPX_TEST_SRCS-yes += keyframe_test.cc
LIBVPX_TEST_SRCS-yes += test_libvpx.cc
LIBVPX_TEST_SRCS-yes += video_source.h

109
test/video_source.h Normal file
View File

@ -0,0 +1,109 @@
/*
* 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.
*/
#ifndef TEST_VIDEO_SOURCE_H_
#define TEST_VIDEO_SOURCE_H_
#include "vpx/vpx_encoder.h"
namespace libvpx_test {
// Abstract base class for test video sources, which provide a stream of
// vpx_image_t images with associated timestamps and duration.
class VideoSource {
public:
virtual ~VideoSource() {}
// Prepare the stream for reading, rewind/open as necessary.
virtual void Begin() = 0;
// Advance the cursor to the next frame
virtual void Next() = 0;
// Get the current video frame, or NULL on End-Of-Stream.
virtual vpx_image_t *img() const = 0;
// Get the presentation timestamp of the current frame.
virtual vpx_codec_pts_t pts() const = 0;
// Get the current frame's duration
virtual unsigned long duration() const = 0;
// Get the timebase for the stream
virtual vpx_rational_t timebase() const = 0;
// Get the current frame counter, starting at 0.
virtual unsigned int frame() const = 0;
};
class DummyVideoSource : public VideoSource {
public:
DummyVideoSource()
: img_(NULL), limit_(100) { SetSize(80, 64); }
virtual ~DummyVideoSource() { vpx_img_free(img_); }
virtual void Begin() {
frame_ = 0;
FillFrame();
}
virtual void Next() {
++frame_;
FillFrame();
}
virtual vpx_image_t *img() const {
return (frame_ < limit_) ? img_ : NULL;
}
// Models a stream where Timebase = 1/FPS, so pts == frame.
virtual vpx_codec_pts_t pts() const { return frame_; }
virtual unsigned long duration() const { return 1; }
virtual vpx_rational_t timebase() const {
const vpx_rational_t t = {1, 30};
return t;
}
virtual unsigned int frame() const { return frame_; }
void SetSize(unsigned int width, unsigned int height) {
vpx_img_free(img_);
raw_sz_ = ((width + 31)&~31) * height * 3 / 2;
img_ = vpx_img_alloc(NULL, VPX_IMG_FMT_VPXI420, width, height, 32);
}
protected:
virtual void FillFrame() { memset(img_->img_data, 0, raw_sz_); }
vpx_image_t *img_;
size_t raw_sz_;
unsigned int limit_;
unsigned int frame_;
};
class RandomVideoSource : public DummyVideoSource {
protected:
// 15 frames of noise, followed by 15 static frames. Reset to 0 rather
// than holding previous frames to encourage keyframes to be thrown.
virtual void FillFrame() {
if (frame_ % 30 < 15)
for (size_t i = 0; i < raw_sz_; ++i)
img_->img_data[i] = rand();
else
memset(img_->img_data, 0, raw_sz_);
}
};
} // namespace libvpx_test
#endif // TEST_VIDEO_SOURCE_H_