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:
parent
2d225689d3
commit
b9180fc049
137
test/encode_test_driver.cc
Normal file
137
test/encode_test_driver.cc
Normal 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
178
test/encode_test_driver.h
Normal 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
103
test/keyframe_test.cc
Normal 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
|
@ -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
109
test/video_source.h
Normal 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_
|
Loading…
x
Reference in New Issue
Block a user