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

Switch GetWallClockTime to using vectors for input and output.

Ensures that all converted timestamps will be monotonically increasing
within a single call to GetWallClockTime.

Performance seems unchanged (unit tests take the same amount of time).

As part of this change, also does the following:
- VideoRendererImpl unit test switched to WallClockTimeSource to avoid
having to reimplement the vector conversion routine.

BUG=485042
TEST=new unittest.

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

Cr-Commit-Position: refs/heads/master@{#329363}
parent 99e8dd40
...@@ -129,7 +129,7 @@ class MockVideoRenderer : public VideoRenderer { ...@@ -129,7 +129,7 @@ class MockVideoRenderer : public VideoRenderer {
const BufferingStateCB& buffering_state_cb, const BufferingStateCB& buffering_state_cb,
const base::Closure& ended_cb, const base::Closure& ended_cb,
const PipelineStatusCB& error_cb, const PipelineStatusCB& error_cb,
const WallClockTimeCB& wall_clock_time_cb, const TimeSource::WallClockTimeCB& wall_clock_time_cb,
const base::Closure& waiting_for_decryption_key_cb)); const base::Closure& waiting_for_decryption_key_cb));
MOCK_METHOD1(Flush, void(const base::Closure& callback)); MOCK_METHOD1(Flush, void(const base::Closure& callback));
MOCK_METHOD1(StartPlayingFrom, void(base::TimeDelta)); MOCK_METHOD1(StartPlayingFrom, void(base::TimeDelta));
...@@ -203,7 +203,9 @@ class MockTimeSource : public TimeSource { ...@@ -203,7 +203,9 @@ class MockTimeSource : public TimeSource {
MOCK_METHOD1(SetPlaybackRate, void(double)); MOCK_METHOD1(SetPlaybackRate, void(double));
MOCK_METHOD1(SetMediaTime, void(base::TimeDelta)); MOCK_METHOD1(SetMediaTime, void(base::TimeDelta));
MOCK_METHOD0(CurrentMediaTime, base::TimeDelta()); MOCK_METHOD0(CurrentMediaTime, base::TimeDelta());
MOCK_METHOD1(GetWallClockTime, base::TimeTicks(base::TimeDelta)); MOCK_METHOD2(GetWallClockTimes,
bool(const std::vector<base::TimeDelta>&,
std::vector<base::TimeTicks>*));
private: private:
DISALLOW_COPY_AND_ASSIGN(MockTimeSource); DISALLOW_COPY_AND_ASSIGN(MockTimeSource);
......
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
#ifndef MEDIA_BASE_TIME_SOURCE_H_ #ifndef MEDIA_BASE_TIME_SOURCE_H_
#define MEDIA_BASE_TIME_SOURCE_H_ #define MEDIA_BASE_TIME_SOURCE_H_
#include <vector>
#include "base/callback.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "media/base/media_export.h" #include "media/base/media_export.h"
...@@ -13,6 +16,11 @@ namespace media { ...@@ -13,6 +16,11 @@ namespace media {
// A TimeSource is capable of providing the current media time. // A TimeSource is capable of providing the current media time.
class MEDIA_EXPORT TimeSource { class MEDIA_EXPORT TimeSource {
public: public:
// Helper alias for converting media timestamps into a wall clock timestamps.
using WallClockTimeCB =
base::Callback<bool(const std::vector<base::TimeDelta>&,
std::vector<base::TimeTicks>*)>;
TimeSource() {} TimeSource() {}
virtual ~TimeSource() {} virtual ~TimeSource() {}
...@@ -26,7 +34,7 @@ class MEDIA_EXPORT TimeSource { ...@@ -26,7 +34,7 @@ class MEDIA_EXPORT TimeSource {
// Updates the current playback rate. It is expected that values from // Updates the current playback rate. It is expected that values from
// CurrentMediaTime() will eventually reflect the new playback rate (e.g., the // CurrentMediaTime() will eventually reflect the new playback rate (e.g., the
// media time will advance at half speed if the rate was set to 0.5f). // media time will advance at half speed if the rate was set to 0.5).
virtual void SetPlaybackRate(double playback_rate) = 0; virtual void SetPlaybackRate(double playback_rate) = 0;
// Sets the media time to start ticking from. Only valid to call while the // Sets the media time to start ticking from. Only valid to call while the
...@@ -41,16 +49,27 @@ class MEDIA_EXPORT TimeSource { ...@@ -41,16 +49,27 @@ class MEDIA_EXPORT TimeSource {
// will never go backwards, the frequency at which they update may be low. // will never go backwards, the frequency at which they update may be low.
virtual base::TimeDelta CurrentMediaTime() = 0; virtual base::TimeDelta CurrentMediaTime() = 0;
// Converts a media timestamp into a wall clock time. If the media time is // Converts a vector of media timestamps into a vector of wall clock times. If
// stopped, returns a null TimeTicks. // the media time is stopped, returns false and does not modify the output
// vector. Returns true and converts all timestamps otherwise. Guarantees that
// wall clock time does not go backwards for monotonically increasing media
// timestamps.
//
// Each timestamp converted from |media_timestamps| will be pushed into
// |wall_clock_times| such that after all timestamps are converted, the two
// vectors are parallel (media_timestamps[i] -> wall_clock_times[i]).
// //
// |media_time| values too far ahead of the current media time will return an // |media_timestamps| values too far ahead of the current media time will
// estimated value; as such, these values may go backwards in time slightly. // be converted to an estimated value; as such, these values may go backwards
// in time slightly between calls to GetWallClockTimes().
// //
// |media_time| values behind the current media time may be significantly // |media_timestamps| values behind the current media time may be
// incorrect if the playback rate has changed recently. The only guarantee is // significantly incorrect if the playback rate has changed recently. The only
// that the returned time will be less than the current wall clock time. // guarantee is that the returned time will be less than the current wall
virtual base::TimeTicks GetWallClockTime(base::TimeDelta media_time) = 0; // clock time.
virtual bool GetWallClockTimes(
const std::vector<base::TimeDelta>& media_timestamps,
std::vector<base::TimeTicks>* wall_clock_times) = 0;
}; };
} // namespace media } // namespace media
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "media/base/decryptor.h" #include "media/base/decryptor.h"
#include "media/base/media_export.h" #include "media/base/media_export.h"
#include "media/base/pipeline_status.h" #include "media/base/pipeline_status.h"
#include "media/base/time_source.h"
namespace media { namespace media {
...@@ -24,9 +25,6 @@ class MEDIA_EXPORT VideoRenderer { ...@@ -24,9 +25,6 @@ class MEDIA_EXPORT VideoRenderer {
// Used to paint VideoFrame. // Used to paint VideoFrame.
typedef base::Callback<void(const scoped_refptr<VideoFrame>&)> PaintCB; typedef base::Callback<void(const scoped_refptr<VideoFrame>&)> PaintCB;
// Used to convert a media timestamp into a wall clock timestamp.
typedef base::Callback<base::TimeTicks(base::TimeDelta)> WallClockTimeCB;
VideoRenderer(); VideoRenderer();
// Stops all operations and fires all pending callbacks. // Stops all operations and fires all pending callbacks.
...@@ -62,7 +60,7 @@ class MEDIA_EXPORT VideoRenderer { ...@@ -62,7 +60,7 @@ class MEDIA_EXPORT VideoRenderer {
const BufferingStateCB& buffering_state_cb, const BufferingStateCB& buffering_state_cb,
const base::Closure& ended_cb, const base::Closure& ended_cb,
const PipelineStatusCB& error_cb, const PipelineStatusCB& error_cb,
const WallClockTimeCB& wall_clock_time_cb, const TimeSource::WallClockTimeCB& wall_clock_time_cb,
const base::Closure& waiting_for_decryption_key_cb) = 0; const base::Closure& waiting_for_decryption_key_cb) = 0;
// Discards any video data and stops reading from |stream|, executing // Discards any video data and stops reading from |stream|, executing
......
...@@ -5,14 +5,11 @@ ...@@ -5,14 +5,11 @@
#include "media/base/wall_clock_time_source.h" #include "media/base/wall_clock_time_source.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/time/default_tick_clock.h"
namespace media { namespace media {
WallClockTimeSource::WallClockTimeSource() WallClockTimeSource::WallClockTimeSource()
: tick_clock_(new base::DefaultTickClock()), : tick_clock_(&default_tick_clock_), ticking_(false), playback_rate_(1.0) {
ticking_(false),
playback_rate_(1.0) {
} }
WallClockTimeSource::~WallClockTimeSource() { WallClockTimeSource::~WallClockTimeSource() {
...@@ -60,25 +57,27 @@ base::TimeDelta WallClockTimeSource::CurrentMediaTime() { ...@@ -60,25 +57,27 @@ base::TimeDelta WallClockTimeSource::CurrentMediaTime() {
return CurrentMediaTime_Locked(); return CurrentMediaTime_Locked();
} }
base::TimeTicks WallClockTimeSource::GetWallClockTime(base::TimeDelta time) { bool WallClockTimeSource::GetWallClockTimes(
const std::vector<base::TimeDelta>& media_timestamps,
std::vector<base::TimeTicks>* wall_clock_times) {
base::AutoLock auto_lock(lock_); base::AutoLock auto_lock(lock_);
if (!ticking_ || playback_rate_ == 0.0) if (!ticking_ || !playback_rate_)
return base::TimeTicks(); return false;
// See notes about |time| values less than |base_time_| in TimeSource header. DCHECK(wall_clock_times->empty());
return reference_wall_ticks_ + wall_clock_times->reserve(media_timestamps.size());
base::TimeDelta::FromMicroseconds( for (const auto& media_timestamp : media_timestamps) {
(time - base_time_).InMicroseconds() / playback_rate_); wall_clock_times->push_back(
} reference_wall_ticks_ +
base::TimeDelta::FromMicroseconds(
void WallClockTimeSource::SetTickClockForTesting( (media_timestamp - base_time_).InMicroseconds() / playback_rate_));
scoped_ptr<base::TickClock> tick_clock) { }
tick_clock_.swap(tick_clock); return true;
} }
base::TimeDelta WallClockTimeSource::CurrentMediaTime_Locked() { base::TimeDelta WallClockTimeSource::CurrentMediaTime_Locked() {
lock_.AssertAcquired(); lock_.AssertAcquired();
if (!ticking_ || playback_rate_ == 0.0) if (!ticking_ || !playback_rate_)
return base_time_; return base_time_;
base::TimeTicks now = tick_clock_->NowTicks(); base::TimeTicks now = tick_clock_->NowTicks();
......
...@@ -7,13 +7,10 @@ ...@@ -7,13 +7,10 @@
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/synchronization/lock.h" #include "base/synchronization/lock.h"
#include "base/time/default_tick_clock.h"
#include "media/base/media_export.h" #include "media/base/media_export.h"
#include "media/base/time_source.h" #include "media/base/time_source.h"
namespace base {
class TickClock;
}
namespace media { namespace media {
// A time source that uses interpolation based on the system clock. // A time source that uses interpolation based on the system clock.
...@@ -28,14 +25,23 @@ class MEDIA_EXPORT WallClockTimeSource : public TimeSource { ...@@ -28,14 +25,23 @@ class MEDIA_EXPORT WallClockTimeSource : public TimeSource {
void SetPlaybackRate(double playback_rate) override; void SetPlaybackRate(double playback_rate) override;
void SetMediaTime(base::TimeDelta time) override; void SetMediaTime(base::TimeDelta time) override;
base::TimeDelta CurrentMediaTime() override; base::TimeDelta CurrentMediaTime() override;
base::TimeTicks GetWallClockTime(base::TimeDelta time) override; bool GetWallClockTimes(
const std::vector<base::TimeDelta>& media_timestamps,
std::vector<base::TimeTicks>* wall_clock_times) override;
void SetTickClockForTesting(scoped_ptr<base::TickClock> tick_clock); void set_tick_clock_for_testing(base::TickClock* tick_clock) {
tick_clock_ = tick_clock;
}
private: private:
base::TimeDelta CurrentMediaTime_Locked(); base::TimeDelta CurrentMediaTime_Locked();
scoped_ptr<base::TickClock> tick_clock_; // Allow for an injectable tick clock for testing.
base::DefaultTickClock default_tick_clock_;
// If specified, used instead of |default_tick_clock_|.
base::TickClock* tick_clock_;
bool ticking_; bool ticking_;
// While ticking we can interpolate the current media time by measuring the // While ticking we can interpolate the current media time by measuring the
......
...@@ -11,8 +11,7 @@ namespace media { ...@@ -11,8 +11,7 @@ namespace media {
class WallClockTimeSourceTest : public testing::Test { class WallClockTimeSourceTest : public testing::Test {
public: public:
WallClockTimeSourceTest() : tick_clock_(new base::SimpleTestTickClock()) { WallClockTimeSourceTest() : tick_clock_(new base::SimpleTestTickClock()) {
time_source_.SetTickClockForTesting( time_source_.set_tick_clock_for_testing(tick_clock_.get());
scoped_ptr<base::TickClock>(tick_clock_));
AdvanceTimeInSeconds(1); AdvanceTimeInSeconds(1);
} }
~WallClockTimeSourceTest() override {} ~WallClockTimeSourceTest() override {}
...@@ -30,33 +29,41 @@ class WallClockTimeSourceTest : public testing::Test { ...@@ -30,33 +29,41 @@ class WallClockTimeSourceTest : public testing::Test {
} }
bool IsWallClockNowForMediaTimeInSeconds(int seconds) { bool IsWallClockNowForMediaTimeInSeconds(int seconds) {
return tick_clock_->NowTicks() == std::vector<base::TimeTicks> wall_clock_times;
time_source_.GetWallClockTime(base::TimeDelta::FromSeconds(seconds)); EXPECT_TRUE(time_source_.GetWallClockTimes(
std::vector<base::TimeDelta>(1, base::TimeDelta::FromSeconds(seconds)),
&wall_clock_times));
return tick_clock_->NowTicks() == wall_clock_times.front();
} }
bool IsWallClockNullForMediaTimeInSeconds(int seconds) { bool IsTimeStopped() {
return time_source_.GetWallClockTime(base::TimeDelta::FromSeconds(seconds)) std::vector<base::TimeTicks> wall_clock_times;
.is_null(); // Convert any random value, it shouldn't matter for this call.
const bool time_stopped = !time_source_.GetWallClockTimes(
std::vector<base::TimeDelta>(1, base::TimeDelta::FromSeconds(1)),
&wall_clock_times);
EXPECT_EQ(time_stopped, wall_clock_times.empty());
return time_stopped;
} }
protected: protected:
WallClockTimeSource time_source_; WallClockTimeSource time_source_;
base::SimpleTestTickClock* tick_clock_; // Owned by |time_source_|. scoped_ptr<base::SimpleTestTickClock> tick_clock_;
DISALLOW_COPY_AND_ASSIGN(WallClockTimeSourceTest); DISALLOW_COPY_AND_ASSIGN(WallClockTimeSourceTest);
}; };
TEST_F(WallClockTimeSourceTest, InitialTimeIsZero) { TEST_F(WallClockTimeSourceTest, InitialTimeIsZero) {
EXPECT_EQ(0, CurrentMediaTimeInSeconds()); EXPECT_EQ(0, CurrentMediaTimeInSeconds());
EXPECT_TRUE(IsWallClockNullForMediaTimeInSeconds(0)); EXPECT_TRUE(IsTimeStopped());
} }
TEST_F(WallClockTimeSourceTest, InitialTimeIsNotTicking) { TEST_F(WallClockTimeSourceTest, InitialTimeIsNotTicking) {
EXPECT_EQ(0, CurrentMediaTimeInSeconds()); EXPECT_EQ(0, CurrentMediaTimeInSeconds());
EXPECT_TRUE(IsWallClockNullForMediaTimeInSeconds(0)); EXPECT_TRUE(IsTimeStopped());
AdvanceTimeInSeconds(100); AdvanceTimeInSeconds(100);
EXPECT_EQ(0, CurrentMediaTimeInSeconds()); EXPECT_EQ(0, CurrentMediaTimeInSeconds());
EXPECT_TRUE(IsWallClockNullForMediaTimeInSeconds(0)); EXPECT_TRUE(IsTimeStopped());
} }
TEST_F(WallClockTimeSourceTest, InitialPlaybackRateIsOne) { TEST_F(WallClockTimeSourceTest, InitialPlaybackRateIsOne) {
...@@ -71,10 +78,10 @@ TEST_F(WallClockTimeSourceTest, InitialPlaybackRateIsOne) { ...@@ -71,10 +78,10 @@ TEST_F(WallClockTimeSourceTest, InitialPlaybackRateIsOne) {
TEST_F(WallClockTimeSourceTest, SetMediaTime) { TEST_F(WallClockTimeSourceTest, SetMediaTime) {
EXPECT_EQ(0, CurrentMediaTimeInSeconds()); EXPECT_EQ(0, CurrentMediaTimeInSeconds());
EXPECT_TRUE(IsWallClockNullForMediaTimeInSeconds(0)); EXPECT_TRUE(IsTimeStopped());
SetMediaTimeInSeconds(10); SetMediaTimeInSeconds(10);
EXPECT_EQ(10, CurrentMediaTimeInSeconds()); EXPECT_EQ(10, CurrentMediaTimeInSeconds());
EXPECT_TRUE(IsWallClockNullForMediaTimeInSeconds(10)); EXPECT_TRUE(IsTimeStopped());
} }
TEST_F(WallClockTimeSourceTest, SetPlaybackRate) { TEST_F(WallClockTimeSourceTest, SetPlaybackRate) {
...@@ -108,7 +115,7 @@ TEST_F(WallClockTimeSourceTest, StopTicking) { ...@@ -108,7 +115,7 @@ TEST_F(WallClockTimeSourceTest, StopTicking) {
AdvanceTimeInSeconds(10); AdvanceTimeInSeconds(10);
EXPECT_EQ(10, CurrentMediaTimeInSeconds()); EXPECT_EQ(10, CurrentMediaTimeInSeconds());
EXPECT_TRUE(IsWallClockNullForMediaTimeInSeconds(10)); EXPECT_TRUE(IsTimeStopped());
} }
} // namespace media } // namespace media
...@@ -16,6 +16,7 @@ const int kMovingAverageSamples = 25; ...@@ -16,6 +16,7 @@ const int kMovingAverageSamples = 25;
VideoRendererAlgorithm::ReadyFrame::ReadyFrame( VideoRendererAlgorithm::ReadyFrame::ReadyFrame(
const scoped_refptr<VideoFrame>& ready_frame) const scoped_refptr<VideoFrame>& ready_frame)
: frame(ready_frame), : frame(ready_frame),
has_estimated_end_time(true),
ideal_render_count(0), ideal_render_count(0),
render_count(0), render_count(0),
drop_count(0) { drop_count(0) {
...@@ -30,13 +31,13 @@ bool VideoRendererAlgorithm::ReadyFrame::operator<( ...@@ -30,13 +31,13 @@ bool VideoRendererAlgorithm::ReadyFrame::operator<(
} }
VideoRendererAlgorithm::VideoRendererAlgorithm( VideoRendererAlgorithm::VideoRendererAlgorithm(
const TimeConverterCB& time_converter_cb) const TimeSource::WallClockTimeCB& wall_clock_time_cb)
: cadence_estimator_(base::TimeDelta::FromSeconds( : cadence_estimator_(base::TimeDelta::FromSeconds(
kMinimumAcceptableTimeBetweenGlitchesSecs)), kMinimumAcceptableTimeBetweenGlitchesSecs)),
time_converter_cb_(time_converter_cb), wall_clock_time_cb_(wall_clock_time_cb),
frame_duration_calculator_(kMovingAverageSamples), frame_duration_calculator_(kMovingAverageSamples),
frame_dropping_disabled_(false) { frame_dropping_disabled_(false) {
DCHECK(!time_converter_cb_.is_null()); DCHECK(!wall_clock_time_cb_.is_null());
Reset(); Reset();
} }
...@@ -393,31 +394,31 @@ void VideoRendererAlgorithm::AccountForMissedIntervals( ...@@ -393,31 +394,31 @@ void VideoRendererAlgorithm::AccountForMissedIntervals(
} }
bool VideoRendererAlgorithm::UpdateFrameStatistics() { bool VideoRendererAlgorithm::UpdateFrameStatistics() {
// Figure out all current ready frame times at once so we minimize the drift DCHECK(!frame_queue_.empty());
// relative to real time as the code below executes.
for (size_t i = 0; i < frame_queue_.size(); ++i) {
ReadyFrame& frame = frame_queue_[i];
const bool new_frame = frame.start_time.is_null();
frame.start_time = time_converter_cb_.Run(frame.frame->timestamp());
// If time stops or never started, exit immediately. // Figure out all current ready frame times at once.
if (frame.start_time.is_null()) { std::vector<base::TimeDelta> media_timestamps;
frame.end_time = base::TimeTicks(); media_timestamps.reserve(frame_queue_.size());
return false; for (const auto& ready_frame : frame_queue_)
} media_timestamps.push_back(ready_frame.frame->timestamp());
// TODO(dalecurtis): An unlucky tick of a playback rate change could cause // If time has stopped, we can bail out early.
// this to skew so much that time goes backwards between calls. Fix this by std::vector<base::TimeTicks> wall_clock_times;
// either converting all timestamps at once or with some retry logic. if (!wall_clock_time_cb_.Run(media_timestamps, &wall_clock_times))
if (i > 0) { return false;
frame_queue_[i - 1].end_time = frame.start_time;
const base::TimeDelta delta = // Transfer the converted wall clock times into our frame queue.
frame.start_time - frame_queue_[i - 1].start_time; DCHECK_EQ(wall_clock_times.size(), frame_queue_.size());
CHECK_GT(delta, base::TimeDelta()); for (size_t i = 0; i < frame_queue_.size() - 1; ++i) {
if (new_frame) ReadyFrame& frame = frame_queue_[i];
frame_duration_calculator_.AddSample(delta); const bool new_sample = frame.has_estimated_end_time;
} frame.start_time = wall_clock_times[i];
frame.end_time = wall_clock_times[i + 1];
frame.has_estimated_end_time = false;
if (new_sample)
frame_duration_calculator_.AddSample(frame.end_time - frame.start_time);
} }
frame_queue_.back().start_time = wall_clock_times.back();
if (!frame_duration_calculator_.count()) if (!frame_duration_calculator_.count())
return false; return false;
......
...@@ -44,10 +44,8 @@ namespace media { ...@@ -44,10 +44,8 @@ namespace media {
// Combined these three approaches enforce optimal smoothness in many cases. // Combined these three approaches enforce optimal smoothness in many cases.
class MEDIA_EXPORT VideoRendererAlgorithm { class MEDIA_EXPORT VideoRendererAlgorithm {
public: public:
// Used to convert a media timestamp into wall clock time. explicit VideoRendererAlgorithm(
using TimeConverterCB = base::Callback<base::TimeTicks(base::TimeDelta)>; const TimeSource::WallClockTimeCB& wall_clock_time_cb);
explicit VideoRendererAlgorithm(const TimeConverterCB& time_converter_cb);
~VideoRendererAlgorithm(); ~VideoRendererAlgorithm();
// Chooses the best frame for the interval [deadline_min, deadline_max] based // Chooses the best frame for the interval [deadline_min, deadline_max] based
...@@ -162,6 +160,10 @@ class MEDIA_EXPORT VideoRendererAlgorithm { ...@@ -162,6 +160,10 @@ class MEDIA_EXPORT VideoRendererAlgorithm {
base::TimeTicks start_time; base::TimeTicks start_time;
base::TimeTicks end_time; base::TimeTicks end_time;
// True if this frame's end time is based on the average frame duration and
// not the time of the next frame.
bool has_estimated_end_time;
int ideal_render_count; int ideal_render_count;
int render_count; int render_count;
int drop_count; int drop_count;
...@@ -178,8 +180,8 @@ class MEDIA_EXPORT VideoRendererAlgorithm { ...@@ -178,8 +180,8 @@ class MEDIA_EXPORT VideoRendererAlgorithm {
// calculate an average frame duration. Updates |cadence_estimator_|. // calculate an average frame duration. Updates |cadence_estimator_|.
// //
// Note: Wall clock time is recomputed each Render() call because it's // Note: Wall clock time is recomputed each Render() call because it's
// expected that the TimeSource powering TimeConverterCB will skew slightly // expected that the TimeSource powering TimeSource::WallClockTimeCB will skew
// based on the audio clock. // slightly based on the audio clock.
// //
// TODO(dalecurtis): Investigate how accurate we need the wall clock times to // TODO(dalecurtis): Investigate how accurate we need the wall clock times to
// be, so we can avoid recomputing every time (we would need to recompute when // be, so we can avoid recomputing every time (we would need to recompute when
...@@ -259,7 +261,7 @@ class MEDIA_EXPORT VideoRendererAlgorithm { ...@@ -259,7 +261,7 @@ class MEDIA_EXPORT VideoRendererAlgorithm {
bool have_rendered_frames_; bool have_rendered_frames_;
// Callback used to convert media timestamps into wall clock timestamps. // Callback used to convert media timestamps into wall clock timestamps.
const TimeConverterCB time_converter_cb_; const TimeSource::WallClockTimeCB wall_clock_time_cb_;
// The last |deadline_max| provided to Render(), used to predict whether // The last |deadline_max| provided to Render(), used to predict whether
// frames were rendered over cadence between Render() calls. // frames were rendered over cadence between Render() calls.
......
...@@ -64,13 +64,12 @@ class VideoRendererAlgorithmTest : public testing::Test { ...@@ -64,13 +64,12 @@ class VideoRendererAlgorithmTest : public testing::Test {
public: public:
VideoRendererAlgorithmTest() VideoRendererAlgorithmTest()
: tick_clock_(new base::SimpleTestTickClock()), : tick_clock_(new base::SimpleTestTickClock()),
algorithm_(base::Bind(&WallClockTimeSource::GetWallClockTime, algorithm_(base::Bind(&WallClockTimeSource::GetWallClockTimes,
base::Unretained(&time_source_))) { base::Unretained(&time_source_))) {
// Always start the TickClock at a non-zero value since null values have // Always start the TickClock at a non-zero value since null values have
// special connotations. // special connotations.
tick_clock_->Advance(base::TimeDelta::FromMicroseconds(10000)); tick_clock_->Advance(base::TimeDelta::FromMicroseconds(10000));
time_source_.SetTickClockForTesting( time_source_.set_tick_clock_for_testing(tick_clock_.get());
scoped_ptr<base::TickClock>(tick_clock_));
} }
~VideoRendererAlgorithmTest() override {} ~VideoRendererAlgorithmTest() override {}
...@@ -301,8 +300,8 @@ class VideoRendererAlgorithmTest : public testing::Test { ...@@ -301,8 +300,8 @@ class VideoRendererAlgorithmTest : public testing::Test {
protected: protected:
VideoFramePool frame_pool_; VideoFramePool frame_pool_;
scoped_ptr<base::SimpleTestTickClock> tick_clock_;
WallClockTimeSource time_source_; WallClockTimeSource time_source_;
base::SimpleTestTickClock* tick_clock_; // Owned by |time_source_|.
VideoRendererAlgorithm algorithm_; VideoRendererAlgorithm algorithm_;
private: private:
...@@ -606,7 +605,7 @@ TEST_F(VideoRendererAlgorithmTest, TimeIsStopped) { ...@@ -606,7 +605,7 @@ TEST_F(VideoRendererAlgorithmTest, TimeIsStopped) {
EXPECT_EQ(tg.interval(1), frame->timestamp()); EXPECT_EQ(tg.interval(1), frame->timestamp());
EXPECT_EQ(0u, frames_dropped); EXPECT_EQ(0u, frames_dropped);
EXPECT_EQ(2u, frames_queued()); EXPECT_EQ(2u, frames_queued());
EXPECT_EQ(2u, algorithm_.EffectiveFramesQueued()); EXPECT_EQ(1u, algorithm_.EffectiveFramesQueued());
} }
// Verify frames inserted out of order end up in the right spot and are rendered // Verify frames inserted out of order end up in the right spot and are rendered
......
...@@ -166,27 +166,39 @@ base::TimeDelta AudioRendererImpl::CurrentMediaTime() { ...@@ -166,27 +166,39 @@ base::TimeDelta AudioRendererImpl::CurrentMediaTime() {
return current_media_time; return current_media_time;
} }
base::TimeTicks AudioRendererImpl::GetWallClockTime(base::TimeDelta time) { bool AudioRendererImpl::GetWallClockTimes(
const std::vector<base::TimeDelta>& media_timestamps,
std::vector<base::TimeTicks>* wall_clock_times) {
base::AutoLock auto_lock(lock_); base::AutoLock auto_lock(lock_);
if (last_render_ticks_.is_null() || playback_rate_ == 0.0) if (last_render_ticks_.is_null() || !playback_rate_)
return base::TimeTicks(); return false;
base::TimeDelta base_time; DCHECK(wall_clock_times->empty());
if (time < audio_clock_->front_timestamp()) { wall_clock_times->reserve(media_timestamps.size());
// See notes about |time| values less than |base_time| in TimeSource header. for (const auto& media_timestamp : media_timestamps) {
base_time = audio_clock_->front_timestamp(); base::TimeDelta base_time;
} else if (time > audio_clock_->back_timestamp()) { if (media_timestamp < audio_clock_->front_timestamp()) {
base_time = audio_clock_->back_timestamp(); // See notes about |media_time| values less than |base_time| in TimeSource
} else { // header.
// No need to estimate time, so return the actual wallclock time. base_time = audio_clock_->front_timestamp();
return last_render_ticks_ + audio_clock_->TimeUntilPlayback(time); } else if (media_timestamp > audio_clock_->back_timestamp()) {
} base_time = audio_clock_->back_timestamp();
} else {
// No need to estimate time, so return the actual wallclock time.
wall_clock_times->push_back(
last_render_ticks_ +
audio_clock_->TimeUntilPlayback(media_timestamp));
continue;
}
// In practice, most calls will be estimates given the relatively small window // In practice, most calls will be estimates given the relatively small
// in which clients can get the actual time. // window in which clients can get the actual time.
return last_render_ticks_ + audio_clock_->TimeUntilPlayback(base_time) + wall_clock_times->push_back(
base::TimeDelta::FromMicroseconds((time - base_time).InMicroseconds() / last_render_ticks_ + audio_clock_->TimeUntilPlayback(base_time) +
playback_rate_); base::TimeDelta::FromMicroseconds(
(media_timestamp - base_time).InMicroseconds() / playback_rate_));
}
return true;
} }
TimeSource* AudioRendererImpl::GetTimeSource() { TimeSource* AudioRendererImpl::GetTimeSource() {
......
...@@ -71,7 +71,9 @@ class MEDIA_EXPORT AudioRendererImpl ...@@ -71,7 +71,9 @@ class MEDIA_EXPORT AudioRendererImpl
void SetPlaybackRate(double rate) override; void SetPlaybackRate(double rate) override;
void SetMediaTime(base::TimeDelta time) override; void SetMediaTime(base::TimeDelta time) override;
base::TimeDelta CurrentMediaTime() override; base::TimeDelta CurrentMediaTime() override;
base::TimeTicks GetWallClockTime(base::TimeDelta time) override; bool GetWallClockTimes(
const std::vector<base::TimeDelta>& media_timestamps,
std::vector<base::TimeTicks>* wall_clock_times) override;
// AudioRenderer implementation. // AudioRenderer implementation.
void Initialize(DemuxerStream* stream, void Initialize(DemuxerStream* stream,
......
...@@ -229,16 +229,21 @@ void RendererImpl::EnableClocklessVideoPlaybackForTesting() { ...@@ -229,16 +229,21 @@ void RendererImpl::EnableClocklessVideoPlaybackForTesting() {
clockless_video_playback_enabled_for_testing_ = true; clockless_video_playback_enabled_for_testing_ = true;
} }
base::TimeTicks RendererImpl::GetWallClockTime(base::TimeDelta time) { bool RendererImpl::GetWallClockTimes(
const std::vector<base::TimeDelta>& media_timestamps,
std::vector<base::TimeTicks>* wall_clock_times) {
// No BelongsToCurrentThread() checking because this can be called from other // No BelongsToCurrentThread() checking because this can be called from other
// threads. // threads.
// //
// TODO(scherkus): Currently called from VideoRendererImpl's internal thread, // TODO(scherkus): Currently called from VideoRendererImpl's internal thread,
// which should go away at some point http://crbug.com/110814 // which should go away at some point http://crbug.com/110814
if (clockless_video_playback_enabled_for_testing_) if (clockless_video_playback_enabled_for_testing_) {
return base::TimeTicks::Now(); *wall_clock_times = std::vector<base::TimeTicks>(media_timestamps.size(),
base::TimeTicks::Now());
return true;
}
return time_source_->GetWallClockTime(time); return time_source_->GetWallClockTimes(media_timestamps, wall_clock_times);
} }
void RendererImpl::SetDecryptorReadyCallback( void RendererImpl::SetDecryptorReadyCallback(
...@@ -336,7 +341,7 @@ void RendererImpl::InitializeVideoRenderer() { ...@@ -336,7 +341,7 @@ void RendererImpl::InitializeVideoRenderer() {
&video_buffering_state_), &video_buffering_state_),
base::Bind(&RendererImpl::OnVideoRendererEnded, weak_this_), base::Bind(&RendererImpl::OnVideoRendererEnded, weak_this_),
base::Bind(&RendererImpl::OnError, weak_this_), base::Bind(&RendererImpl::OnError, weak_this_),
base::Bind(&RendererImpl::GetWallClockTime, base::Unretained(this)), base::Bind(&RendererImpl::GetWallClockTimes, base::Unretained(this)),
waiting_for_decryption_key_cb_); waiting_for_decryption_key_cb_);
} }
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#ifndef MEDIA_RENDERERS_RENDERER_IMPL_H_ #ifndef MEDIA_RENDERERS_RENDERER_IMPL_H_
#define MEDIA_RENDERERS_RENDERER_IMPL_H_ #define MEDIA_RENDERERS_RENDERER_IMPL_H_
#include <vector>
#include "base/cancelable_callback.h" #include "base/cancelable_callback.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
...@@ -80,7 +82,8 @@ class MEDIA_EXPORT RendererImpl : public Renderer { ...@@ -80,7 +82,8 @@ class MEDIA_EXPORT RendererImpl : public Renderer {
STATE_ERROR STATE_ERROR
}; };
base::TimeTicks GetWallClockTime(base::TimeDelta time); bool GetWallClockTimes(const std::vector<base::TimeDelta>& media_timestamps,
std::vector<base::TimeTicks>* wall_clock_times);
// Requests that this object notifies when a decryptor is ready through the // Requests that this object notifies when a decryptor is ready through the
// |decryptor_ready_cb| provided. // |decryptor_ready_cb| provided.
......
...@@ -144,7 +144,7 @@ void VideoRendererImpl::Initialize( ...@@ -144,7 +144,7 @@ void VideoRendererImpl::Initialize(
const BufferingStateCB& buffering_state_cb, const BufferingStateCB& buffering_state_cb,
const base::Closure& ended_cb, const base::Closure& ended_cb,
const PipelineStatusCB& error_cb, const PipelineStatusCB& error_cb,
const WallClockTimeCB& wall_clock_time_cb, const TimeSource::WallClockTimeCB& wall_clock_time_cb,
const base::Closure& waiting_for_decryption_key_cb) { const base::Closure& waiting_for_decryption_key_cb) {
DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(task_runner_->BelongsToCurrentThread());
base::AutoLock auto_lock(lock_); base::AutoLock auto_lock(lock_);
...@@ -346,7 +346,7 @@ void VideoRendererImpl::ThreadMain() { ...@@ -346,7 +346,7 @@ void VideoRendererImpl::ThreadMain() {
} }
base::TimeTicks target_paint_time = base::TimeTicks target_paint_time =
wall_clock_time_cb_.Run(ready_frames_.front()->timestamp()); ConvertMediaTimestamp(ready_frames_.front()->timestamp());
// If media time has stopped, don't attempt to paint any more frames. // If media time has stopped, don't attempt to paint any more frames.
if (target_paint_time.is_null()) { if (target_paint_time.is_null()) {
...@@ -420,7 +420,7 @@ void VideoRendererImpl::PaintNextReadyFrame_Locked() { ...@@ -420,7 +420,7 @@ void VideoRendererImpl::PaintNextReadyFrame_Locked() {
scoped_refptr<VideoFrame> next_frame = ready_frames_.front(); scoped_refptr<VideoFrame> next_frame = ready_frames_.front();
ready_frames_.pop_front(); ready_frames_.pop_front();
last_media_time_ = wall_clock_time_cb_.Run(next_frame->timestamp()); last_media_time_ = ConvertMediaTimestamp(next_frame->timestamp());
paint_cb_.Run(next_frame); paint_cb_.Run(next_frame);
...@@ -435,8 +435,7 @@ void VideoRendererImpl::DropNextReadyFrame_Locked() { ...@@ -435,8 +435,7 @@ void VideoRendererImpl::DropNextReadyFrame_Locked() {
lock_.AssertAcquired(); lock_.AssertAcquired();
last_media_time_ = last_media_time_ = ConvertMediaTimestamp(ready_frames_.front()->timestamp());
wall_clock_time_cb_.Run(ready_frames_.front()->timestamp());
ready_frames_.pop_front(); ready_frames_.pop_front();
frames_dropped_++; frames_dropped_++;
...@@ -736,4 +735,13 @@ size_t VideoRendererImpl::MaybeFireEndedCallback() { ...@@ -736,4 +735,13 @@ size_t VideoRendererImpl::MaybeFireEndedCallback() {
return effective_frames; return effective_frames;
} }
base::TimeTicks VideoRendererImpl::ConvertMediaTimestamp(
base::TimeDelta media_time) {
std::vector<base::TimeDelta> media_times(1, media_time);
std::vector<base::TimeTicks> wall_clock_times;
if (!wall_clock_time_cb_.Run(media_times, &wall_clock_times))
return base::TimeTicks();
return wall_clock_times[0];
}
} // namespace media } // namespace media
...@@ -65,7 +65,7 @@ class MEDIA_EXPORT VideoRendererImpl ...@@ -65,7 +65,7 @@ class MEDIA_EXPORT VideoRendererImpl
const BufferingStateCB& buffering_state_cb, const BufferingStateCB& buffering_state_cb,
const base::Closure& ended_cb, const base::Closure& ended_cb,
const PipelineStatusCB& error_cb, const PipelineStatusCB& error_cb,
const WallClockTimeCB& wall_clock_time_cb, const TimeSource::WallClockTimeCB& wall_clock_time_cb,
const base::Closure& waiting_for_decryption_key_cb) override; const base::Closure& waiting_for_decryption_key_cb) override;
void Flush(const base::Closure& callback) override; void Flush(const base::Closure& callback) override;
void StartPlayingFrom(base::TimeDelta timestamp) override; void StartPlayingFrom(base::TimeDelta timestamp) override;
...@@ -147,6 +147,9 @@ class MEDIA_EXPORT VideoRendererImpl ...@@ -147,6 +147,9 @@ class MEDIA_EXPORT VideoRendererImpl
// does so. Returns algorithm_->EffectiveFramesQueued(). // does so. Returns algorithm_->EffectiveFramesQueued().
size_t MaybeFireEndedCallback(); size_t MaybeFireEndedCallback();
// Helper method for converting a single media timestamp to wall clock time.
base::TimeTicks ConvertMediaTimestamp(base::TimeDelta media_timestamp);
scoped_refptr<base::SingleThreadTaskRunner> task_runner_; scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
// Enables the use of VideoRendererAlgorithm and VideoRendererSink for frame // Enables the use of VideoRendererAlgorithm and VideoRendererSink for frame
...@@ -229,7 +232,7 @@ class MEDIA_EXPORT VideoRendererImpl ...@@ -229,7 +232,7 @@ class MEDIA_EXPORT VideoRendererImpl
BufferingStateCB buffering_state_cb_; BufferingStateCB buffering_state_cb_;
base::Closure ended_cb_; base::Closure ended_cb_;
PipelineStatusCB error_cb_; PipelineStatusCB error_cb_;
WallClockTimeCB wall_clock_time_cb_; TimeSource::WallClockTimeCB wall_clock_time_cb_;
base::TimeDelta start_timestamp_; base::TimeDelta start_timestamp_;
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include "media/base/null_video_sink.h" #include "media/base/null_video_sink.h"
#include "media/base/test_helpers.h" #include "media/base/test_helpers.h"
#include "media/base/video_frame.h" #include "media/base/video_frame.h"
#include "media/base/wall_clock_time_source.h"
#include "media/renderers/video_renderer_impl.h" #include "media/renderers/video_renderer_impl.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -66,6 +67,7 @@ class VideoRendererImplTest : public testing::TestWithParam<bool> { ...@@ -66,6 +67,7 @@ class VideoRendererImplTest : public testing::TestWithParam<bool> {
renderer_->disable_new_video_renderer_for_testing(); renderer_->disable_new_video_renderer_for_testing();
renderer_->SetTickClockForTesting(scoped_ptr<base::TickClock>(tick_clock_)); renderer_->SetTickClockForTesting(scoped_ptr<base::TickClock>(tick_clock_));
null_video_sink_->set_tick_clock_for_testing(tick_clock_); null_video_sink_->set_tick_clock_for_testing(tick_clock_);
time_source_.set_tick_clock_for_testing(tick_clock_);
// Start wallclock time at a non-zero value. // Start wallclock time at a non-zero value.
AdvanceWallclockTimeInMs(12345); AdvanceWallclockTimeInMs(12345);
...@@ -108,8 +110,9 @@ class VideoRendererImplTest : public testing::TestWithParam<bool> { ...@@ -108,8 +110,9 @@ class VideoRendererImplTest : public testing::TestWithParam<bool> {
PipelineStatus decoder_status) { PipelineStatus decoder_status) {
if (low_delay) if (low_delay)
demuxer_stream_.set_liveness(DemuxerStream::LIVENESS_LIVE); demuxer_stream_.set_liveness(DemuxerStream::LIVENESS_LIVE);
EXPECT_CALL(*decoder_, Initialize(_, _, _, _)).WillOnce( EXPECT_CALL(*decoder_, Initialize(_, _, _, _))
DoAll(SaveArg<3>(&output_cb_), RunCallback<2>(decoder_status))); .WillOnce(
DoAll(SaveArg<3>(&output_cb_), RunCallback<2>(decoder_status)));
EXPECT_CALL(*this, OnWaitingForDecryptionKey()).Times(0); EXPECT_CALL(*this, OnWaitingForDecryptionKey()).Times(0);
renderer_->Initialize( renderer_->Initialize(
&demuxer_stream_, status_cb, media::SetDecryptorReadyCB(), &demuxer_stream_, status_cb, media::SetDecryptorReadyCB(),
...@@ -118,16 +121,18 @@ class VideoRendererImplTest : public testing::TestWithParam<bool> { ...@@ -118,16 +121,18 @@ class VideoRendererImplTest : public testing::TestWithParam<bool> {
base::Bind(&StrictMock<MockCB>::BufferingStateChange, base::Bind(&StrictMock<MockCB>::BufferingStateChange,
base::Unretained(&mock_cb_)), base::Unretained(&mock_cb_)),
ended_event_.GetClosure(), error_event_.GetPipelineStatusCB(), ended_event_.GetClosure(), error_event_.GetPipelineStatusCB(),
base::Bind(&VideoRendererImplTest::GetWallClockTime, base::Bind(&WallClockTimeSource::GetWallClockTimes,
base::Unretained(this)), base::Unretained(&time_source_)),
base::Bind(&VideoRendererImplTest::OnWaitingForDecryptionKey, base::Bind(&VideoRendererImplTest::OnWaitingForDecryptionKey,
base::Unretained(this))); base::Unretained(this)));
} }
void StartPlayingFrom(int milliseconds) { void StartPlayingFrom(int milliseconds) {
SCOPED_TRACE(base::StringPrintf("StartPlayingFrom(%d)", milliseconds)); SCOPED_TRACE(base::StringPrintf("StartPlayingFrom(%d)", milliseconds));
renderer_->StartPlayingFrom( const base::TimeDelta media_time =
base::TimeDelta::FromMilliseconds(milliseconds)); base::TimeDelta::FromMilliseconds(milliseconds);
time_source_.SetMediaTime(media_time);
renderer_->StartPlayingFrom(media_time);
message_loop_.RunUntilIdle(); message_loop_.RunUntilIdle();
} }
...@@ -263,6 +268,9 @@ class VideoRendererImplTest : public testing::TestWithParam<bool> { ...@@ -263,6 +268,9 @@ class VideoRendererImplTest : public testing::TestWithParam<bool> {
DCHECK_EQ(&message_loop_, base::MessageLoop::current()); DCHECK_EQ(&message_loop_, base::MessageLoop::current());
base::AutoLock l(lock_); base::AutoLock l(lock_);
time_ += base::TimeDelta::FromMilliseconds(time_ms); time_ += base::TimeDelta::FromMilliseconds(time_ms);
time_source_.StopTicking();
time_source_.SetMediaTime(time_);
time_source_.StartTicking();
} }
bool has_ended() const { bool has_ended() const {
...@@ -289,12 +297,9 @@ class VideoRendererImplTest : public testing::TestWithParam<bool> { ...@@ -289,12 +297,9 @@ class VideoRendererImplTest : public testing::TestWithParam<bool> {
PipelineStatistics last_pipeline_statistics_; PipelineStatistics last_pipeline_statistics_;
private: WallClockTimeSource time_source_;
base::TimeTicks GetWallClockTime(base::TimeDelta time) {
base::AutoLock l(lock_);
return tick_clock_->NowTicks() + (time - time_);
}
private:
void DecodeRequested(const scoped_refptr<DecoderBuffer>& buffer, void DecodeRequested(const scoped_refptr<DecoderBuffer>& buffer,
const VideoDecoder::DecodeCB& decode_cb) { const VideoDecoder::DecodeCB& decode_cb) {
DCHECK_EQ(&message_loop_, base::MessageLoop::current()); DCHECK_EQ(&message_loop_, base::MessageLoop::current());
...@@ -434,6 +439,7 @@ TEST_P(VideoRendererImplTest, DecodeError_Playing) { ...@@ -434,6 +439,7 @@ TEST_P(VideoRendererImplTest, DecodeError_Playing) {
EXPECT_CALL(mock_cb_, BufferingStateChange(BUFFERING_HAVE_ENOUGH)); EXPECT_CALL(mock_cb_, BufferingStateChange(BUFFERING_HAVE_ENOUGH));
StartPlayingFrom(0); StartPlayingFrom(0);
renderer_->OnTimeStateChanged(true); renderer_->OnTimeStateChanged(true);
time_source_.StartTicking();
AdvanceTimeInMs(10); AdvanceTimeInMs(10);
QueueFrames("error"); QueueFrames("error");
...@@ -496,6 +502,8 @@ TEST_P(VideoRendererImplTest, StartPlayingFrom_LowDelay) { ...@@ -496,6 +502,8 @@ TEST_P(VideoRendererImplTest, StartPlayingFrom_LowDelay) {
SatisfyPendingRead(); SatisfyPendingRead();
renderer_->OnTimeStateChanged(true); renderer_->OnTimeStateChanged(true);
time_source_.StartTicking();
WaitableMessageLoopEvent event; WaitableMessageLoopEvent event;
EXPECT_CALL(mock_cb_, FrameReceived(HasTimestamp(10))) EXPECT_CALL(mock_cb_, FrameReceived(HasTimestamp(10)))
.WillOnce(RunClosure(event.GetClosure())); .WillOnce(RunClosure(event.GetClosure()));
...@@ -545,10 +553,17 @@ TEST_P(VideoRendererImplTest, Underflow) { ...@@ -545,10 +553,17 @@ TEST_P(VideoRendererImplTest, Underflow) {
{ {
SCOPED_TRACE("Waiting for frame drops"); SCOPED_TRACE("Waiting for frame drops");
WaitableMessageLoopEvent event; WaitableMessageLoopEvent event;
EXPECT_CALL(mock_cb_, FrameReceived(HasTimestamp(30)))
.Times(0); // Note: Starting the TimeSource will cause the old VideoRendererImpl to
EXPECT_CALL(mock_cb_, FrameReceived(HasTimestamp(60))) // start rendering frames on its own thread, so the first frame may be
.Times(0); // received.
time_source_.StartTicking();
if (GetParam())
EXPECT_CALL(mock_cb_, FrameReceived(HasTimestamp(30))).Times(0);
else
EXPECT_CALL(mock_cb_, FrameReceived(HasTimestamp(30))).Times(AnyNumber());
EXPECT_CALL(mock_cb_, FrameReceived(HasTimestamp(60))).Times(0);
EXPECT_CALL(mock_cb_, FrameReceived(HasTimestamp(90))) EXPECT_CALL(mock_cb_, FrameReceived(HasTimestamp(90)))
.WillOnce(RunClosure(event.GetClosure())); .WillOnce(RunClosure(event.GetClosure()));
AdvanceTimeInMs(91); AdvanceTimeInMs(91);
...@@ -675,6 +690,7 @@ TEST_P(VideoRendererImplTest, RenderingStartedThenStopped) { ...@@ -675,6 +690,7 @@ TEST_P(VideoRendererImplTest, RenderingStartedThenStopped) {
} }
renderer_->OnTimeStateChanged(true); renderer_->OnTimeStateChanged(true);
time_source_.StartTicking();
// Suspend all future callbacks and synthetically advance the media time, // Suspend all future callbacks and synthetically advance the media time,
// because this is a background render, we won't underflow by waiting until // because this is a background render, we won't underflow by waiting until
......
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