Commit b3a547e7 authored by dalecurtis's avatar dalecurtis Committed by Commit bot

Implement support for 2-pattern cadence.

An N pattern cadence is simply figuring out if N times the
frame duration has an integer cadence (M) relative to the
render interval, then figuring out how to distribute N
values such that sum(x1 + x2 + x3 ... xN) == M.

I haven't dug through our discussion on how to compute this
for N > 2, but for N == 2, it's simple to just use
x1 == ceil(M/2), x2 == floor(M/2)

This change refactors the main function in VideoCadenceEstimator
into smaller pieces (an original review request :) in order to
reuse them for detecting 2-pattern cadence.

BUG=439548
TEST=all unit tests still pass, 3:2 cadence tests pass.

Review URL: https://codereview.chromium.org/1125893002

Cr-Commit-Position: refs/heads/master@{#329813}
parent f936368a
This diff is collapsed.
This diff is collapsed.
......@@ -3,6 +3,8 @@
// found in the LICENSE file.
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "media/filters/video_cadence_estimator.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -22,33 +24,47 @@ static base::TimeDelta Interval(double hertz) {
return base::TimeDelta::FromSecondsD(1.0 / hertz);
}
static void VerifyCadence(VideoCadenceEstimator* estimator,
double frame_hertz,
double render_hertz,
int expected_cadence) {
std::vector<int> CreateCadenceFromString(const std::string& cadence) {
std::vector<std::string> tokens;
CHECK_EQ('[', cadence[0]);
CHECK_EQ(']', cadence[cadence.length() - 1]);
base::SplitString(cadence.substr(1, cadence.length() - 2), ':', &tokens);
std::vector<int> result;
for (const auto& token : tokens) {
int cadence_value = 0;
CHECK(base::StringToInt(token, &cadence_value)) << token;
result.push_back(cadence_value);
}
return result;
}
static void VerifyCadenceVector(VideoCadenceEstimator* estimator,
double frame_hertz,
double render_hertz,
const std::string& expected_cadence) {
SCOPED_TRACE(base::StringPrintf("Checking %.03f fps into %0.03f", frame_hertz,
render_hertz));
const std::vector<int> expected_cadence_vector =
CreateCadenceFromString(expected_cadence);
estimator->Reset();
const base::TimeDelta acceptable_drift = Interval(frame_hertz) / 2;
const bool cadence_changed = estimator->UpdateCadenceEstimate(
Interval(render_hertz), Interval(frame_hertz), acceptable_drift);
EXPECT_EQ(cadence_changed, estimator->has_cadence());
EXPECT_EQ(!!expected_cadence, estimator->has_cadence());
EXPECT_EQ(expected_cadence_vector.empty(), !estimator->has_cadence());
// Nothing further to test.
if (!expected_cadence)
if (expected_cadence_vector.empty())
return;
// Spot check a few frame indices.
if (frame_hertz <= render_hertz) {
EXPECT_EQ(expected_cadence, estimator->GetCadenceForFrame(0));
EXPECT_EQ(expected_cadence, estimator->GetCadenceForFrame(1));
EXPECT_EQ(expected_cadence, estimator->GetCadenceForFrame(2));
} else {
EXPECT_EQ(1, estimator->GetCadenceForFrame(0));
EXPECT_EQ(0, estimator->GetCadenceForFrame(1));
EXPECT_EQ(1, estimator->GetCadenceForFrame(expected_cadence));
EXPECT_EQ(0, estimator->GetCadenceForFrame(expected_cadence + 1));
// Spot two cycles of the cadence.
for (size_t i = 0; i < expected_cadence_vector.size() * 2; ++i) {
ASSERT_EQ(expected_cadence_vector[i % expected_cadence_vector.size()],
estimator->GetCadenceForFrame(i));
}
}
......@@ -58,28 +74,31 @@ TEST(VideoCadenceEstimatorTest, CadenceCalculations) {
base::TimeDelta::FromSeconds(kMinimumAcceptableTimeBetweenGlitchesSecs));
estimator.set_cadence_hysteresis_threshold_for_testing(base::TimeDelta());
VerifyCadence(&estimator, 24, 60, 0);
VerifyCadence(&estimator, NTSC(24), 60, 0);
VerifyCadence(&estimator, 25, 60, 0);
VerifyCadence(&estimator, NTSC(30), 60, 2);
VerifyCadence(&estimator, 30, 60, 2);
VerifyCadence(&estimator, 50, 60, 0);
VerifyCadence(&estimator, NTSC(60), 60, 1);
VerifyCadence(&estimator, 120, 60, 2);
const std::string kEmptyCadence = "[]";
VerifyCadenceVector(&estimator, 24, 60, "[3:2]");
VerifyCadenceVector(&estimator, NTSC(24), 60, "[3:2]");
VerifyCadenceVector(&estimator, 25, 60, kEmptyCadence);
VerifyCadenceVector(&estimator, NTSC(30), 60, "[2]");
VerifyCadenceVector(&estimator, 30, 60, "[2]");
VerifyCadenceVector(&estimator, 50, 60, kEmptyCadence);
VerifyCadenceVector(&estimator, NTSC(60), 60, "[1]");
VerifyCadenceVector(&estimator, 120, 60, "[1:0]");
VerifyCadenceVector(&estimator, 120, 24, "[1:0:0:0:0]");
// 50Hz is common in the EU.
VerifyCadence(&estimator, NTSC(24), 50, 0);
VerifyCadence(&estimator, 24, 50, 0);
VerifyCadence(&estimator, NTSC(25), 50, 2);
VerifyCadence(&estimator, 25, 50, 2);
VerifyCadence(&estimator, NTSC(30), 50, 0);
VerifyCadence(&estimator, 30, 50, 0);
VerifyCadence(&estimator, NTSC(60), 50, 0);
VerifyCadence(&estimator, 60, 50, 0);
VerifyCadence(&estimator, 25, NTSC(60), 0);
VerifyCadence(&estimator, 120, NTSC(60), 0);
VerifyCadence(&estimator, 1, NTSC(60), 60);
VerifyCadenceVector(&estimator, NTSC(24), 50, kEmptyCadence);
VerifyCadenceVector(&estimator, 24, 50, kEmptyCadence);
VerifyCadenceVector(&estimator, NTSC(25), 50, "[2]");
VerifyCadenceVector(&estimator, 25, 50, "[2]");
VerifyCadenceVector(&estimator, NTSC(30), 50, kEmptyCadence);
VerifyCadenceVector(&estimator, 30, 50, kEmptyCadence);
VerifyCadenceVector(&estimator, NTSC(60), 50, kEmptyCadence);
VerifyCadenceVector(&estimator, 60, 50, kEmptyCadence);
VerifyCadenceVector(&estimator, 25, NTSC(60), kEmptyCadence);
VerifyCadenceVector(&estimator, 120, NTSC(60), kEmptyCadence);
VerifyCadenceVector(&estimator, 1, NTSC(60), "[60]");
}
TEST(VideoCadenceEstimatorTest, CadenceVariesWithAcceptableDrift) {
......@@ -101,7 +120,7 @@ TEST(VideoCadenceEstimatorTest, CadenceVariesWithAcceptableDrift) {
EXPECT_TRUE(estimator.UpdateCadenceEstimate(render_interval, frame_interval,
acceptable_drift));
EXPECT_TRUE(estimator.has_cadence());
EXPECT_EQ(2, estimator.get_cadence_for_testing());
EXPECT_EQ("[1:0]", estimator.GetCadenceForTesting());
}
TEST(VideoCadenceEstimatorTest, CadenceVariesWithAcceptableGlitchTime) {
......@@ -125,7 +144,7 @@ TEST(VideoCadenceEstimatorTest, CadenceVariesWithAcceptableGlitchTime) {
EXPECT_TRUE(estimator->UpdateCadenceEstimate(render_interval, frame_interval,
acceptable_drift));
EXPECT_TRUE(estimator->has_cadence());
EXPECT_EQ(2, estimator->get_cadence_for_testing());
EXPECT_EQ("[1:0]", estimator->GetCadenceForTesting());
}
TEST(VideoCadenceEstimatorTest, CadenceHystersisPreventsOscillation) {
......
......@@ -94,10 +94,12 @@ scoped_refptr<VideoFrame> VideoRendererAlgorithm::Render(
base::TimeDelta selected_frame_drift;
// Step 4: Attempt to find the best frame by cadence.
bool was_frame_selected_by_cadence = false;
int cadence_overage = 0;
int frame_to_render =
FindBestFrameByCadence(first_frame_ ? nullptr : &cadence_overage);
if (frame_to_render >= 0) {
was_frame_selected_by_cadence = true;
selected_frame_drift =
CalculateAbsoluteDriftForFrame(deadline_min, frame_to_render);
}
......@@ -121,7 +123,12 @@ scoped_refptr<VideoFrame> VideoRendererAlgorithm::Render(
}
if (frame_to_render >= 0) {
cadence_overage = 0;
if (was_frame_selected_by_cadence) {
cadence_overage = 0;
was_frame_selected_by_cadence = false;
DVLOG(2) << "Overriding frame selected by cadence because of drift: "
<< selected_frame_drift;
}
selected_frame_drift =
CalculateAbsoluteDriftForFrame(deadline_min, frame_to_render);
}
......@@ -132,7 +139,10 @@ scoped_refptr<VideoFrame> VideoRendererAlgorithm::Render(
// selection is going to be bad because it means no suitable frame has any
// coverage of the deadline interval.
if (frame_to_render < 0 || selected_frame_drift > max_acceptable_drift_) {
cadence_overage = 0;
if (was_frame_selected_by_cadence) {
cadence_overage = 0;
was_frame_selected_by_cadence = false;
}
frame_to_render = FindBestFrameByDrift(deadline_min, &selected_frame_drift);
}
......@@ -189,11 +199,14 @@ scoped_refptr<VideoFrame> VideoRendererAlgorithm::Render(
}
}
// Increment the frame counter for all frames removed after the last
// rendered frame.
cadence_frame_counter_ += frame_to_render - last_frame_index_;
frame_queue_.erase(frame_queue_.begin(),
frame_queue_.begin() + frame_to_render);
}
if (last_render_had_glitch_) {
if (last_render_had_glitch_ && !first_frame_) {
DVLOG(2) << "Deadline: [" << deadline_min.ToInternalValue() << ", "
<< deadline_max.ToInternalValue()
<< "], Interval: " << render_interval_.InMicroseconds()
......@@ -215,6 +228,14 @@ scoped_refptr<VideoFrame> VideoRendererAlgorithm::Render(
deadline_min >= frame_queue_.front().start_time - render_interval_ / 2) {
frame_queue_.front().render_count += cadence_overage + 1;
frame_queue_.front().drop_count += cadence_overage;
// Once we reach a glitch in our cadence sequence, reset the base frame
// number used for defining the cadence sequence.
if (!was_frame_selected_by_cadence && cadence_estimator_.has_cadence()) {
cadence_frame_counter_ = 0;
UpdateCadenceForFrames();
}
first_frame_ = false;
}
......@@ -235,8 +256,9 @@ size_t VideoRendererAlgorithm::RemoveExpiredFrames(base::TimeTicks deadline) {
// Finds and removes all frames which are too old to be used; I.e., the end of
// their render interval is further than |max_acceptable_drift_| from the
// given |deadline|.
size_t frames_to_expire = 0;
// given |deadline|. We also always expire anything inserted before the last
// rendered frame.
size_t frames_to_expire = last_frame_index_;
const base::TimeTicks minimum_start_time =
deadline - max_acceptable_drift_ - average_frame_duration_;
for (; frames_to_expire < frame_queue_.size() - 1; ++frames_to_expire) {
......@@ -247,6 +269,7 @@ size_t VideoRendererAlgorithm::RemoveExpiredFrames(base::TimeTicks deadline) {
if (!frames_to_expire)
return 0;
cadence_frame_counter_ += frames_to_expire - last_frame_index_;
frame_queue_.erase(frame_queue_.begin(),
frame_queue_.begin() + frames_to_expire);
......@@ -282,6 +305,7 @@ void VideoRendererAlgorithm::Reset() {
cadence_estimator_.Reset();
frame_duration_calculator_.Reset();
first_frame_ = true;
cadence_frame_counter_ = 0;
// Default to ATSC IS/191 recommendations for maximum acceptable drift before
// we have enough frames to base the maximum on frame duration.
......@@ -474,6 +498,7 @@ bool VideoRendererAlgorithm::UpdateFrameStatistics() {
if (!cadence_changed)
return true;
cadence_frame_counter_ = 0;
UpdateCadenceForFrames();
// Thus far there appears to be no need for special 3:2 considerations, the
......@@ -489,7 +514,8 @@ void VideoRendererAlgorithm::UpdateCadenceForFrames() {
// cadence selection.
frame_queue_[i].ideal_render_count =
cadence_estimator_.has_cadence()
? cadence_estimator_.GetCadenceForFrame(i - last_frame_index_)
? cadence_estimator_.GetCadenceForFrame(cadence_frame_counter_ +
(i - last_frame_index_))
: 0;
}
}
......
......@@ -189,7 +189,8 @@ class MEDIA_EXPORT VideoRendererAlgorithm {
bool UpdateFrameStatistics();
// Updates the ideal render count for all frames in |frame_queue_| based on
// the cadence returned by |cadence_estimator_|.
// the cadence returned by |cadence_estimator_|. Cadence is assigned based
// on |frame_counter_|.
void UpdateCadenceForFrames();
// If |cadence_estimator_| has detected a valid cadence, attempts to find the
......@@ -292,6 +293,13 @@ class MEDIA_EXPORT VideoRendererAlgorithm {
// until the first frame has reached its presentation time.
bool first_frame_;
// The frame number of the last rendered frame; incremented for every frame
// rendered and every frame dropped or expired since the last rendered frame.
//
// Given to |cadence_estimator_| when assigning cadence values to the
// ReadyFrameQueue. Cleared when a new cadence is detected.
uint64_t cadence_frame_counter_;
DISALLOW_COPY_AND_ASSIGN(VideoRendererAlgorithm);
};
......
......@@ -109,7 +109,7 @@ class VideoRendererAlgorithmTest : public testing::Test {
size_t frames_queued() const { return algorithm_.frame_queue_.size(); }
int GetCadence(double frame_rate, double display_rate) {
std::string GetCadence(double frame_rate, double display_rate) {
TickGenerator display_tg(tick_clock_->NowTicks(), display_rate);
TickGenerator frame_tg(base::TimeTicks(), frame_rate);
time_source_.StartTicking();
......@@ -122,7 +122,8 @@ class VideoRendererAlgorithmTest : public testing::Test {
EXPECT_TRUE(RenderAndStep(&display_tg, &frames_dropped));
// Store cadence before reseting the algorithm.
const int cadence = algorithm_.cadence_estimator_.get_cadence_for_testing();
const std::string cadence =
algorithm_.cadence_estimator_.GetCadenceForTesting();
time_source_.StopTicking();
algorithm_.Reset();
return cadence;
......@@ -230,7 +231,7 @@ class VideoRendererAlgorithmTest : public testing::Test {
// The frame estimate should be off by at most one frame.
const size_t estimated_frames_queued =
frames_queued() /
algorithm_.cadence_estimator_.get_cadence_for_testing();
algorithm_.cadence_estimator_.cadence_size_for_testing();
ASSERT_NEAR(algorithm_.EffectiveFramesQueued(), estimated_frames_queued,
1);
}
......@@ -888,10 +889,10 @@ TEST_F(VideoRendererAlgorithmTest, BestFrameByFractionalCadence) {
}
}
// Verify a 3:2 frame pattern for 23.974fps in 60Hz; doubles as a test for best
// frame by coverage.
// Verify a 3:2 frame pattern for 23.974fps and 24fps in 60Hz.
TEST_F(VideoRendererAlgorithmTest, FilmCadence) {
const double kTestRates[] = {NTSC(24), 24};
disable_cadence_hysteresis();
for (double frame_rate : kTestRates) {
scoped_refptr<VideoFrame> current_frame;
......@@ -916,7 +917,7 @@ TEST_F(VideoRendererAlgorithmTest, FilmCadence) {
}
current_frame = frame;
ASSERT_FALSE(is_using_cadence());
ASSERT_TRUE(is_using_cadence());
});
if (HasFatalFailure())
......@@ -926,28 +927,28 @@ TEST_F(VideoRendererAlgorithmTest, FilmCadence) {
// Spot check common display and frame rate pairs for correctness.
TEST_F(VideoRendererAlgorithmTest, CadenceCalculations) {
ASSERT_FALSE(GetCadence(24, 60));
ASSERT_FALSE(GetCadence(NTSC(24), 60));
ASSERT_FALSE(GetCadence(25, 60));
ASSERT_EQ(2, GetCadence(NTSC(30), 60));
ASSERT_EQ(2, GetCadence(30, 60));
ASSERT_FALSE(GetCadence(50, 60));
ASSERT_EQ(1, GetCadence(NTSC(60), 60));
ASSERT_EQ(2, GetCadence(120, 60));
ASSERT_EQ("[3:2]", GetCadence(24, 60));
ASSERT_EQ("[3:2]", GetCadence(NTSC(24), 60));
ASSERT_EQ("[]", GetCadence(25, 60));
ASSERT_EQ("[2]", GetCadence(NTSC(30), 60));
ASSERT_EQ("[2]", GetCadence(30, 60));
ASSERT_EQ("[]", GetCadence(50, 60));
ASSERT_EQ("[1]", GetCadence(NTSC(60), 60));
ASSERT_EQ("[1:0]", GetCadence(120, 60));
// 50Hz is common in the EU.
ASSERT_FALSE(GetCadence(NTSC(24), 50));
ASSERT_FALSE(GetCadence(24, 50));
ASSERT_EQ(2, GetCadence(NTSC(25), 50));
ASSERT_EQ(2, GetCadence(25, 50));
ASSERT_FALSE(GetCadence(NTSC(30), 50));
ASSERT_FALSE(GetCadence(30, 50));
ASSERT_FALSE(GetCadence(NTSC(60), 50));
ASSERT_FALSE(GetCadence(60, 50));
ASSERT_FALSE(GetCadence(25, NTSC(60)));
ASSERT_EQ(2, GetCadence(120, NTSC(60)));
ASSERT_EQ(60, GetCadence(1, NTSC(60)));
ASSERT_EQ("[]", GetCadence(NTSC(24), 50));
ASSERT_EQ("[]", GetCadence(24, 50));
ASSERT_EQ("[2]", GetCadence(NTSC(25), 50));
ASSERT_EQ("[2]", GetCadence(25, 50));
ASSERT_EQ("[]", GetCadence(NTSC(30), 50));
ASSERT_EQ("[]", GetCadence(30, 50));
ASSERT_EQ("[]", GetCadence(NTSC(60), 50));
ASSERT_EQ("[]", GetCadence(60, 50));
ASSERT_EQ("[]", GetCadence(25, NTSC(60)));
ASSERT_EQ("[1:0]", GetCadence(120, NTSC(60)));
ASSERT_EQ("[60]", GetCadence(1, NTSC(60)));
}
TEST_F(VideoRendererAlgorithmTest, RemoveExpiredFrames) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment