summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authordalecurtis <dalecurtis@chromium.org>2015-06-09 20:17:55 -0700
committerCommit bot <commit-bot@chromium.org>2015-06-10 03:18:25 +0000
commit8a047ffa05b477e148428c787e86601d21d73ce2 (patch)
tree1a6fc9a4fec1d964ec6c5f48eec2f7dcf0772ce9 /media
parent938e803e0a762fde0935ad7005e8f84d84f7e36e (diff)
downloadchromium_src-8a047ffa05b477e148428c787e86601d21d73ce2.zip
chromium_src-8a047ffa05b477e148428c787e86601d21d73ce2.tar.gz
chromium_src-8a047ffa05b477e148428c787e86601d21d73ce2.tar.bz2
Improve audio/video sync during underflow, reduce underflow frequency.
The current approach doesn't account for expired frames when resuming after underflow, which means we may resume and then immediately fail again; these frames are also out of sync with the audio playing out. Fixing this requires several changes to the pipeline: - TimeSource::ConvertMediaTimestamps() will now always convert timestamps, even when time is stopped. When time is stopped the converted values are based on the last known media wall clock time. - TimeSource::ConvertMediaTimestamps() will now return the current media wall clock time when an empty timestamps vector is given; this value is used to figure out which video frames have been played out already. - Introduce AudioClock::CompensateForSuspendedWrites() to ensure the audio delay value is correctly used when return the current wall clock time. - Introduce |was_time_moving_| to VideoRendererAlgorithm to ensure the |last_deadline_max_| value, used by RemoveExpiredFrames, is only changed when time is ticking; otherwise the value given to RemoveExpiredFrames is overwritten by Render() calls and EffectiveFramesQueued() will vary. - Modifies VideoRendererImpl::FrameReady() to remove expired frames based on the last known media wall clock time during underflow states. Combined these changes lead to a noticeable improvement in audio/video sync during underflow as well as a greatly reduced number of underflow events. BUG=498525 TEST=lots of new tests. Review URL: https://codereview.chromium.org/1160853006 Cr-Commit-Position: refs/heads/master@{#333672}
Diffstat (limited to 'media')
-rw-r--r--media/base/time_source.h9
-rw-r--r--media/base/wall_clock_time_source.cc43
-rw-r--r--media/base/wall_clock_time_source.h4
-rw-r--r--media/base/wall_clock_time_source_unittest.cc66
-rw-r--r--media/filters/audio_clock.cc21
-rw-r--r--media/filters/audio_clock.h12
-rw-r--r--media/filters/audio_clock_unittest.cc26
-rw-r--r--media/filters/video_renderer_algorithm.cc38
-rw-r--r--media/filters/video_renderer_algorithm.h16
-rw-r--r--media/filters/video_renderer_algorithm_unittest.cc38
-rw-r--r--media/renderers/audio_renderer_impl.cc70
-rw-r--r--media/renderers/audio_renderer_impl.h7
-rw-r--r--media/renderers/audio_renderer_impl_unittest.cc144
-rw-r--r--media/renderers/video_renderer_impl.cc47
14 files changed, 438 insertions, 103 deletions
diff --git a/media/base/time_source.h b/media/base/time_source.h
index 08cf0f0..496eced 100644
--- a/media/base/time_source.h
+++ b/media/base/time_source.h
@@ -49,9 +49,12 @@ class MEDIA_EXPORT TimeSource {
// will never go backwards, the frequency at which they update may be low.
virtual base::TimeDelta CurrentMediaTime() = 0;
- // Converts a vector of media timestamps into a vector of wall clock times. If
- // the media time is stopped, returns false and does not modify the output
- // vector. Returns true and converts all timestamps otherwise.
+ // Converts a vector of media timestamps into a vector of wall clock times; if
+ // the media time is stopped, returns false, otherwise returns true. Even if
+ // time is stopped, timestamps will be converted.
+ //
+ // Passing an empty |media_timestamps| vector will return the last known media
+ // time as a wall clock time.
//
// Within a single call to GetWallClockTimes() the returned wall clock times
// are a strictly increasing function of the given media times. There is no
diff --git a/media/base/wall_clock_time_source.cc b/media/base/wall_clock_time_source.cc
index 1c59be9..33ec372 100644
--- a/media/base/wall_clock_time_source.cc
+++ b/media/base/wall_clock_time_source.cc
@@ -20,16 +20,16 @@ void WallClockTimeSource::StartTicking() {
base::AutoLock auto_lock(lock_);
DCHECK(!ticking_);
ticking_ = true;
- reference_wall_ticks_ = tick_clock_->NowTicks();
+ reference_time_ = tick_clock_->NowTicks();
}
void WallClockTimeSource::StopTicking() {
DVLOG(1) << __FUNCTION__;
base::AutoLock auto_lock(lock_);
DCHECK(ticking_);
- base_time_ = CurrentMediaTime_Locked();
+ base_timestamp_ = CurrentMediaTime_Locked();
ticking_ = false;
- reference_wall_ticks_ = tick_clock_->NowTicks();
+ reference_time_ = tick_clock_->NowTicks();
}
void WallClockTimeSource::SetPlaybackRate(double playback_rate) {
@@ -38,8 +38,8 @@ void WallClockTimeSource::SetPlaybackRate(double playback_rate) {
// Estimate current media time using old rate to use as a new base time for
// the new rate.
if (ticking_) {
- base_time_ = CurrentMediaTime_Locked();
- reference_wall_ticks_ = tick_clock_->NowTicks();
+ base_timestamp_ = CurrentMediaTime_Locked();
+ reference_time_ = tick_clock_->NowTicks();
}
playback_rate_ = playback_rate;
@@ -49,7 +49,7 @@ void WallClockTimeSource::SetMediaTime(base::TimeDelta time) {
DVLOG(1) << __FUNCTION__ << "(" << time.InMicroseconds() << ")";
base::AutoLock auto_lock(lock_);
CHECK(!ticking_);
- base_time_ = time;
+ base_timestamp_ = time;
}
base::TimeDelta WallClockTimeSource::CurrentMediaTime() {
@@ -61,29 +61,34 @@ bool WallClockTimeSource::GetWallClockTimes(
const std::vector<base::TimeDelta>& media_timestamps,
std::vector<base::TimeTicks>* wall_clock_times) {
base::AutoLock auto_lock(lock_);
- if (!ticking_ || !playback_rate_)
- return false;
-
DCHECK(wall_clock_times->empty());
- wall_clock_times->reserve(media_timestamps.size());
- for (const auto& media_timestamp : media_timestamps) {
- wall_clock_times->push_back(
- reference_wall_ticks_ +
- base::TimeDelta::FromMicroseconds(
- (media_timestamp - base_time_).InMicroseconds() / playback_rate_));
+
+ if (media_timestamps.empty()) {
+ wall_clock_times->push_back(reference_time_);
+ } else {
+ // When playback is paused (rate is zero), assume a rate of 1.0.
+ const double playback_rate = playback_rate_ ? playback_rate_ : 1.0;
+
+ wall_clock_times->reserve(media_timestamps.size());
+ for (const auto& media_timestamp : media_timestamps) {
+ wall_clock_times->push_back(reference_time_ +
+ (media_timestamp - base_timestamp_) /
+ playback_rate);
+ }
}
- return true;
+
+ return playback_rate_ && ticking_;
}
base::TimeDelta WallClockTimeSource::CurrentMediaTime_Locked() {
lock_.AssertAcquired();
if (!ticking_ || !playback_rate_)
- return base_time_;
+ return base_timestamp_;
base::TimeTicks now = tick_clock_->NowTicks();
- return base_time_ +
+ return base_timestamp_ +
base::TimeDelta::FromMicroseconds(
- (now - reference_wall_ticks_).InMicroseconds() * playback_rate_);
+ (now - reference_time_).InMicroseconds() * playback_rate_);
}
} // namespace media
diff --git a/media/base/wall_clock_time_source.h b/media/base/wall_clock_time_source.h
index 2228319..4046e8e 100644
--- a/media/base/wall_clock_time_source.h
+++ b/media/base/wall_clock_time_source.h
@@ -48,8 +48,8 @@ class MEDIA_EXPORT WallClockTimeSource : public TimeSource {
// delta between our reference ticks and the current system ticks and scaling
// that time by the playback rate.
double playback_rate_;
- base::TimeDelta base_time_;
- base::TimeTicks reference_wall_ticks_;
+ base::TimeDelta base_timestamp_;
+ base::TimeTicks reference_time_;
// TODO(scherkus): Remove internal locking from this class after access to
// Renderer::CurrentMediaTime() is single threaded http://crbug.com/370634
diff --git a/media/base/wall_clock_time_source_unittest.cc b/media/base/wall_clock_time_source_unittest.cc
index d9b632f..f5f0bb3 100644
--- a/media/base/wall_clock_time_source_unittest.cc
+++ b/media/base/wall_clock_time_source_unittest.cc
@@ -28,22 +28,26 @@ class WallClockTimeSourceTest : public testing::Test {
return time_source_.SetMediaTime(base::TimeDelta::FromSeconds(seconds));
}
- bool IsWallClockNowForMediaTimeInSeconds(int seconds) {
+ base::TimeTicks ConvertMediaTime(base::TimeDelta timestamp,
+ bool* is_time_moving) {
std::vector<base::TimeTicks> wall_clock_times;
- 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();
+ *is_time_moving = time_source_.GetWallClockTimes(
+ std::vector<base::TimeDelta>(1, timestamp), &wall_clock_times);
+ return wall_clock_times[0];
+ }
+
+ bool IsWallClockNowForMediaTimeInSeconds(int seconds) {
+ bool is_time_moving = false;
+ return tick_clock_->NowTicks() ==
+ ConvertMediaTime(base::TimeDelta::FromSeconds(seconds),
+ &is_time_moving);
}
bool IsTimeStopped() {
- std::vector<base::TimeTicks> wall_clock_times;
+ bool is_time_moving = false;
// 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;
+ ConvertMediaTime(base::TimeDelta::FromSeconds(1), &is_time_moving);
+ return !is_time_moving;
}
protected:
@@ -118,4 +122,44 @@ TEST_F(WallClockTimeSourceTest, StopTicking) {
EXPECT_TRUE(IsTimeStopped());
}
+TEST_F(WallClockTimeSourceTest, ConvertsTimestampsWhenStopped) {
+ const base::TimeDelta kOneSecond = base::TimeDelta::FromSeconds(1);
+ bool is_time_moving = false;
+ EXPECT_EQ(base::TimeTicks(),
+ ConvertMediaTime(base::TimeDelta(), &is_time_moving));
+ EXPECT_FALSE(is_time_moving);
+ EXPECT_NE(base::TimeTicks(), ConvertMediaTime(kOneSecond, &is_time_moving));
+ EXPECT_FALSE(is_time_moving);
+ time_source_.StartTicking();
+ time_source_.StopTicking();
+ EXPECT_EQ(tick_clock_->NowTicks(),
+ ConvertMediaTime(base::TimeDelta(), &is_time_moving));
+ EXPECT_FALSE(is_time_moving);
+ EXPECT_EQ(tick_clock_->NowTicks() + kOneSecond,
+ ConvertMediaTime(kOneSecond, &is_time_moving));
+ EXPECT_FALSE(is_time_moving);
+}
+
+TEST_F(WallClockTimeSourceTest, EmptyMediaTimestampsReturnMediaWallClockTime) {
+ std::vector<base::TimeTicks> wall_clock_times;
+ bool is_time_moving = time_source_.GetWallClockTimes(
+ std::vector<base::TimeDelta>(), &wall_clock_times);
+ EXPECT_FALSE(is_time_moving);
+ EXPECT_EQ(base::TimeTicks(), wall_clock_times[0]);
+
+ wall_clock_times.clear();
+ time_source_.StartTicking();
+ is_time_moving = time_source_.GetWallClockTimes(
+ std::vector<base::TimeDelta>(), &wall_clock_times);
+ EXPECT_TRUE(is_time_moving);
+ EXPECT_EQ(tick_clock_->NowTicks(), wall_clock_times[0]);
+
+ wall_clock_times.clear();
+ time_source_.StopTicking();
+ is_time_moving = time_source_.GetWallClockTimes(
+ std::vector<base::TimeDelta>(), &wall_clock_times);
+ EXPECT_FALSE(is_time_moving);
+ EXPECT_EQ(tick_clock_->NowTicks(), wall_clock_times[0]);
+}
+
} // namespace media
diff --git a/media/filters/audio_clock.cc b/media/filters/audio_clock.cc
index aebc5e5..d93db1d 100644
--- a/media/filters/audio_clock.cc
+++ b/media/filters/audio_clock.cc
@@ -36,7 +36,7 @@ void AudioClock::WroteAudio(int frames_written,
// First write: initialize buffer with silence.
if (start_timestamp_ == front_timestamp_ && buffered_.empty())
- PushBufferedAudioData(delay_frames, 0.0f);
+ PushBufferedAudioData(delay_frames, 0.0);
// Move frames from |buffered_| into the computed timestamp based on
// |delay_frames|.
@@ -47,7 +47,7 @@ void AudioClock::WroteAudio(int frames_written,
std::max(INT64_C(0), total_buffered_frames_ - delay_frames);
front_timestamp_ += ComputeBufferedMediaTime(frames_played);
PushBufferedAudioData(frames_written, playback_rate);
- PushBufferedAudioData(frames_requested - frames_written, 0.0f);
+ PushBufferedAudioData(frames_requested - frames_written, 0.0);
PopBufferedAudioData(frames_played);
back_timestamp_ += base::TimeDelta::FromMicroseconds(
@@ -80,6 +80,23 @@ void AudioClock::WroteAudio(int frames_written,
microseconds_per_frame_);
}
+void AudioClock::CompensateForSuspendedWrites(base::TimeDelta elapsed,
+ int delay_frames) {
+ const int64_t frames_elapsed =
+ elapsed.InMicroseconds() / microseconds_per_frame_ + 0.5;
+
+ // No need to do anything if we're within the limits of our played out audio
+ // or there are no delay frames, the next WroteAudio() call will expire
+ // everything correctly.
+ if (frames_elapsed < total_buffered_frames_ || !delay_frames)
+ return;
+
+ // Otherwise, flush everything and prime with the delay frames.
+ WroteAudio(0, 0, 0, 0);
+ DCHECK(buffered_.empty());
+ PushBufferedAudioData(delay_frames, 0.0);
+}
+
base::TimeDelta AudioClock::TimeUntilPlayback(base::TimeDelta timestamp) const {
DCHECK_GE(timestamp, front_timestamp_);
DCHECK_LE(timestamp, back_timestamp_);
diff --git a/media/filters/audio_clock.h b/media/filters/audio_clock.h
index fe462aba..f7711f6 100644
--- a/media/filters/audio_clock.h
+++ b/media/filters/audio_clock.h
@@ -59,6 +59,18 @@ class MEDIA_EXPORT AudioClock {
int delay_frames,
double playback_rate);
+ // If WroteAudio() calls are suspended (i.e. due to playback being paused) the
+ // AudioClock will not properly advance time (even though all data up until
+ // back_timestamp() will playout on the physical device).
+ //
+ // To compensate for this, when calls resume, before the next WroteAudio(),
+ // callers should call CompensateForSuspendedWrites() to advance the clock for
+ // audio which continued playing out while WroteAudio() calls were suspended.
+ //
+ // |delay_frames| must be provided to properly prime the clock to compensate
+ // for a new initial delay.
+ void CompensateForSuspendedWrites(base::TimeDelta elapsed, int delay_frames);
+
// Returns the bounds of media data currently buffered by the audio hardware,
// taking silence and changes in playback rate into account. Buffered audio
// structure and timestamps are updated with every call to WroteAudio().
diff --git a/media/filters/audio_clock_unittest.cc b/media/filters/audio_clock_unittest.cc
index 3fe437e..2144bb0 100644
--- a/media/filters/audio_clock_unittest.cc
+++ b/media/filters/audio_clock_unittest.cc
@@ -330,4 +330,30 @@ TEST_F(AudioClockTest, SupportsYearsWorthOfAudioData) {
EXPECT_EQ((huge * 2).InDays(), ContiguousAudioDataBufferedInDays());
}
+TEST_F(AudioClockTest, CompensateForSuspendedWrites) {
+ // Buffer 6 seconds of delay and 1 second of audio data.
+ WroteAudio(10, 10, 60, 1.0);
+
+ // Media timestamp zero has to wait for silence to pass.
+ const int kBaseTimeMs = 6000;
+ EXPECT_EQ(kBaseTimeMs, TimeUntilPlaybackInMilliseconds(0));
+
+ // Elapsing frames less than we have buffered should do nothing.
+ const int kDelayFrames = 2;
+ for (int i = 1000; i <= kBaseTimeMs; i += 1000) {
+ clock_.CompensateForSuspendedWrites(base::TimeDelta::FromMilliseconds(i),
+ kDelayFrames);
+ EXPECT_EQ(kBaseTimeMs - (i - 1000), TimeUntilPlaybackInMilliseconds(0));
+
+ // Write silence to simulate maintaining a 7s output buffer.
+ WroteAudio(0, 10, 60, 1.0);
+ }
+
+ // Exhausting all frames should advance timestamps and prime the buffer with
+ // our delay frames value.
+ clock_.CompensateForSuspendedWrites(base::TimeDelta::FromMilliseconds(7000),
+ kDelayFrames);
+ EXPECT_EQ(kDelayFrames * 100, TimeUntilPlaybackInMilliseconds(1000));
+}
+
} // namespace media
diff --git a/media/filters/video_renderer_algorithm.cc b/media/filters/video_renderer_algorithm.cc
index d29cad8..2670823 100644
--- a/media/filters/video_renderer_algorithm.cc
+++ b/media/filters/video_renderer_algorithm.cc
@@ -69,28 +69,24 @@ scoped_refptr<VideoFrame> VideoRendererAlgorithm::Render(
// to Render(). If so, we assume the last frame provided was rendered during
// those intervals and adjust its render count appropriately.
AccountForMissedIntervals(deadline_min, deadline_max);
- last_deadline_max_ = deadline_max;
// Step 3: Update the wall clock timestamps and frame duration estimates for
// all frames currently in the |frame_queue_|.
- if (!UpdateFrameStatistics()) {
- DVLOG(2) << "Failed to update frame statistics.";
-
+ UpdateFrameStatistics();
+ const bool have_known_duration = average_frame_duration_ > base::TimeDelta();
+ if (!(was_time_moving_ && have_known_duration)) {
ReadyFrame& ready_frame = frame_queue_[last_frame_index_];
DCHECK(ready_frame.frame);
// If duration is unknown, we don't have enough frames to make a good guess
// about which frame to use, so always choose the first.
- if (average_frame_duration_ == base::TimeDelta() &&
- !ready_frame.start_time.is_null()) {
+ if (was_time_moving_ && !have_known_duration)
++ready_frame.render_count;
- }
return ready_frame.frame;
}
- DCHECK_GT(average_frame_duration_, base::TimeDelta());
-
+ last_deadline_max_ = deadline_max;
base::TimeDelta selected_frame_drift;
// Step 4: Attempt to find the best frame by cadence.
@@ -250,7 +246,8 @@ size_t VideoRendererAlgorithm::RemoveExpiredFrames(base::TimeTicks deadline) {
if (deadline > last_deadline_max_)
last_deadline_max_ = deadline;
- if (!UpdateFrameStatistics() || frame_queue_.size() < 2)
+ UpdateFrameStatistics();
+ if (frame_queue_.size() < 2)
return 0;
DCHECK_GT(average_frame_duration_, base::TimeDelta());
@@ -308,6 +305,7 @@ void VideoRendererAlgorithm::Reset() {
first_frame_ = true;
cadence_frame_counter_ = 0;
last_render_ignored_cadence_frame_ = false;
+ was_time_moving_ = false;
// Default to ATSC IS/191 recommendations for maximum acceptable drift before
// we have enough frames to base the maximum on frame duration.
@@ -414,7 +412,7 @@ void VideoRendererAlgorithm::AccountForMissedIntervals(
base::TimeTicks deadline_min,
base::TimeTicks deadline_max) {
if (last_deadline_max_.is_null() || deadline_min <= last_deadline_max_ ||
- !have_rendered_frames_) {
+ !have_rendered_frames_ || !was_time_moving_) {
return;
}
@@ -445,7 +443,7 @@ void VideoRendererAlgorithm::AccountForMissedIntervals(
ready_frame.render_count += render_cycle_count;
}
-bool VideoRendererAlgorithm::UpdateFrameStatistics() {
+void VideoRendererAlgorithm::UpdateFrameStatistics() {
DCHECK(!frame_queue_.empty());
// Figure out all current ready frame times at once.
@@ -454,10 +452,9 @@ bool VideoRendererAlgorithm::UpdateFrameStatistics() {
for (const auto& ready_frame : frame_queue_)
media_timestamps.push_back(ready_frame.frame->timestamp());
- // If time has stopped, we can bail out early.
std::vector<base::TimeTicks> wall_clock_times;
- if (!wall_clock_time_cb_.Run(media_timestamps, &wall_clock_times))
- return false;
+ was_time_moving_ =
+ wall_clock_time_cb_.Run(media_timestamps, &wall_clock_times);
// Transfer the converted wall clock times into our frame queue.
DCHECK_EQ(wall_clock_times.size(), frame_queue_.size());
@@ -473,7 +470,7 @@ bool VideoRendererAlgorithm::UpdateFrameStatistics() {
frame_queue_.back().start_time = wall_clock_times.back();
if (!frame_duration_calculator_.count())
- return false;
+ return;
// Compute |average_frame_duration_|, a moving average of the last few frames;
// see kMovingAverageSamples for the exact number.
@@ -496,7 +493,7 @@ bool VideoRendererAlgorithm::UpdateFrameStatistics() {
// If we were called via RemoveExpiredFrames() and Render() was never called,
// we may not have a render interval yet.
if (render_interval_ == base::TimeDelta())
- return true;
+ return;
const bool cadence_changed = cadence_estimator_.UpdateCadenceEstimate(
render_interval_, average_frame_duration_, max_acceptable_drift_);
@@ -504,15 +501,10 @@ bool VideoRendererAlgorithm::UpdateFrameStatistics() {
// No need to update cadence if there's been no change; cadence will be set
// as frames are added to the queue.
if (!cadence_changed)
- return true;
+ return;
cadence_frame_counter_ = 0;
UpdateCadenceForFrames();
-
- // Thus far there appears to be no need for special 3:2 considerations, the
- // smoothness scores seem to naturally fit that pattern based on maximizing
- // frame coverage.
- return true;
}
void VideoRendererAlgorithm::UpdateCadenceForFrames() {
diff --git a/media/filters/video_renderer_algorithm.h b/media/filters/video_renderer_algorithm.h
index 4a3da25..71d251f 100644
--- a/media/filters/video_renderer_algorithm.h
+++ b/media/filters/video_renderer_algorithm.h
@@ -117,6 +117,10 @@ class MEDIA_EXPORT VideoRendererAlgorithm {
// be counted as effective.
size_t EffectiveFramesQueued() const;
+ // Tells the algorithm that Render() callbacks have been suspended for a known
+ // reason and such stoppage shouldn't be counted against future frames.
+ void set_time_stopped() { was_time_moving_ = false; }
+
size_t frames_queued() const { return frame_queue_.size(); }
// Returns the average of the duration of all frames in |frame_queue_|
@@ -175,9 +179,8 @@ class MEDIA_EXPORT VideoRendererAlgorithm {
base::TimeTicks deadline_max);
// Updates the render count and wall clock timestamps for all frames in
- // |frame_queue_|. Returns false if statistics can't be updated at this time;
- // which occurs if media time has stopped or there are not enough frames to
- // calculate an average frame duration. Updates |cadence_estimator_|.
+ // |frame_queue_|. Updates |was_time_stopped_|, |cadence_estimator_| and
+ // |frame_duration_calculator_|.
//
// Note: Wall clock time is recomputed each Render() call because it's
// expected that the TimeSource powering TimeSource::WallClockTimeCB will skew
@@ -186,7 +189,7 @@ class MEDIA_EXPORT VideoRendererAlgorithm {
// 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
// playback rate changes occur though).
- bool UpdateFrameStatistics();
+ void UpdateFrameStatistics();
// Updates the ideal render count for all frames in |frame_queue_| based on
// the cadence returned by |cadence_estimator_|. Cadence is assigned based
@@ -304,6 +307,11 @@ class MEDIA_EXPORT VideoRendererAlgorithm {
// by cadence in favor of one by drift or coverage.
bool last_render_ignored_cadence_frame_;
+ // Indicates if time was moving, set to the return value from
+ // UpdateFrameStatistics() during Render() or externally by
+ // set_time_stopped().
+ bool was_time_moving_;
+
DISALLOW_COPY_AND_ASSIGN(VideoRendererAlgorithm);
};
diff --git a/media/filters/video_renderer_algorithm_unittest.cc b/media/filters/video_renderer_algorithm_unittest.cc
index d8b2eb0..fcdedd6 100644
--- a/media/filters/video_renderer_algorithm_unittest.cc
+++ b/media/filters/video_renderer_algorithm_unittest.cc
@@ -385,6 +385,40 @@ TEST_F(VideoRendererAlgorithmTest, AccountForMissingIntervals) {
ASSERT_TRUE(frame);
EXPECT_EQ(tg.interval(3), frame->timestamp());
EXPECT_EQ(0u, frames_dropped);
+ EXPECT_EQ(1, GetCurrentFrameDisplayCount());
+
+ // Stop the time source and verify AccountForMissedIntervals() doesn't try to
+ // account for intervals from pause behavior.
+ time_source_.StopTicking();
+ frame = RenderAndStep(&tg, &frames_dropped);
+ ASSERT_TRUE(frame);
+ EXPECT_EQ(tg.interval(3), frame->timestamp());
+ EXPECT_EQ(0u, frames_dropped);
+ EXPECT_EQ(1, GetCurrentFrameDisplayCount());
+
+ tg.step(100);
+ frame = RenderAndStep(&tg, &frames_dropped);
+ ASSERT_TRUE(frame);
+ EXPECT_EQ(tg.interval(3), frame->timestamp());
+ EXPECT_EQ(0u, frames_dropped);
+ EXPECT_EQ(1, GetCurrentFrameDisplayCount());
+
+ time_source_.StartTicking();
+
+ // Now run the same test using set_time_stopped();
+ frame = RenderAndStep(&tg, &frames_dropped);
+ ASSERT_TRUE(frame);
+ EXPECT_EQ(tg.interval(3), frame->timestamp());
+ EXPECT_EQ(0u, frames_dropped);
+ EXPECT_EQ(2, GetCurrentFrameDisplayCount());
+
+ algorithm_.set_time_stopped();
+ tg.step(100);
+ frame = RenderAndStep(&tg, &frames_dropped);
+ ASSERT_TRUE(frame);
+ EXPECT_EQ(tg.interval(3), frame->timestamp());
+ EXPECT_EQ(0u, frames_dropped);
+ EXPECT_EQ(3, GetCurrentFrameDisplayCount());
}
TEST_F(VideoRendererAlgorithmTest, OnLastFrameDropped) {
@@ -1201,6 +1235,7 @@ TEST_F(VideoRendererAlgorithmTest, EnqueueFrames) {
RenderAndStep(&tg, &frames_dropped);
EXPECT_EQ(1u, frames_queued());
EXPECT_EQ(frame_1, rendered_frame);
+ EXPECT_EQ(1, GetCurrentFrameDisplayCount());
// The replaced frame should count as dropped.
EXPECT_EQ(1u, frames_dropped);
@@ -1213,6 +1248,7 @@ TEST_F(VideoRendererAlgorithmTest, EnqueueFrames) {
EXPECT_EQ(1u, frames_queued());
EXPECT_EQ(frame_1, rendered_frame);
EXPECT_EQ(1u, frames_dropped);
+ EXPECT_EQ(2, GetCurrentFrameDisplayCount());
// Trying to add a frame < 1 ms after the last frame should drop the frame.
algorithm_.EnqueueFrame(CreateFrame(base::TimeDelta::FromMicroseconds(999)));
@@ -1220,6 +1256,7 @@ TEST_F(VideoRendererAlgorithmTest, EnqueueFrames) {
EXPECT_EQ(1u, frames_queued());
EXPECT_EQ(frame_1, rendered_frame);
EXPECT_EQ(1u, frames_dropped);
+ EXPECT_EQ(3, GetCurrentFrameDisplayCount());
scoped_refptr<VideoFrame> frame_3 = CreateFrame(tg.interval(1));
algorithm_.EnqueueFrame(frame_3);
@@ -1232,6 +1269,7 @@ TEST_F(VideoRendererAlgorithmTest, EnqueueFrames) {
EXPECT_EQ(1u, frames_queued());
EXPECT_EQ(frame_3, rendered_frame);
EXPECT_EQ(1u, frames_dropped);
+ EXPECT_EQ(1, GetCurrentFrameDisplayCount());
}
TEST_F(VideoRendererAlgorithmTest, CadenceForFutureFrames) {
diff --git a/media/renderers/audio_renderer_impl.cc b/media/renderers/audio_renderer_impl.cc
index 59f05b3..7a2c029 100644
--- a/media/renderers/audio_renderer_impl.cc
+++ b/media/renderers/audio_renderer_impl.cc
@@ -14,6 +14,7 @@
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/single_thread_task_runner.h"
+#include "base/time/default_tick_clock.h"
#include "media/base/audio_buffer.h"
#include "media/base/audio_buffer_converter.h"
#include "media/base/audio_hardware_config.h"
@@ -52,6 +53,7 @@ AudioRendererImpl::AudioRendererImpl(
audio_buffer_stream_(
new AudioBufferStream(task_runner, decoders.Pass(), media_log)),
hardware_config_(hardware_config),
+ tick_clock_(new base::DefaultTickClock()),
playback_rate_(0.0),
state_(kUninitialized),
buffering_state_(BUFFERING_HAVE_NOTHING),
@@ -171,38 +173,59 @@ bool AudioRendererImpl::GetWallClockTimes(
const std::vector<base::TimeDelta>& media_timestamps,
std::vector<base::TimeTicks>* wall_clock_times) {
base::AutoLock auto_lock(lock_);
- if (last_render_time_.is_null() || !stop_rendering_time_.is_null() ||
- !playback_rate_ || buffering_state_ != BUFFERING_HAVE_ENOUGH ||
- !sink_playing_) {
- return false;
+ DCHECK(wall_clock_times->empty());
+
+ // When playback is paused (rate is zero), assume a rate of 1.0.
+ const double playback_rate = playback_rate_ ? playback_rate_ : 1.0;
+ const bool is_time_moving = sink_playing_ && playback_rate_ &&
+ !last_render_time_.is_null() &&
+ stop_rendering_time_.is_null();
+
+ // Pre-compute the time until playback of the audio buffer extents, since
+ // these values are frequently used below.
+ const base::TimeDelta time_until_front =
+ audio_clock_->TimeUntilPlayback(audio_clock_->front_timestamp());
+ const base::TimeDelta time_until_back =
+ audio_clock_->TimeUntilPlayback(audio_clock_->back_timestamp());
+
+ if (media_timestamps.empty()) {
+ // Return the current media time as a wall clock time while accounting for
+ // frames which may be in the process of play out.
+ wall_clock_times->push_back(std::min(
+ std::max(tick_clock_->NowTicks(), last_render_time_ + time_until_front),
+ last_render_time_ + time_until_back));
+ return is_time_moving;
}
- DCHECK(wall_clock_times->empty());
wall_clock_times->reserve(media_timestamps.size());
for (const auto& media_timestamp : media_timestamps) {
- base::TimeDelta base_time;
- if (media_timestamp < audio_clock_->front_timestamp()) {
- // See notes about |media_time| values less than |base_time| in TimeSource
- // header.
- base_time = audio_clock_->front_timestamp();
- } 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.
+ // When time was or is moving and the requested media timestamp is within
+ // range of played out audio, we can provide an exact conversion.
+ if (!last_render_time_.is_null() &&
+ media_timestamp >= audio_clock_->front_timestamp() &&
+ media_timestamp <= audio_clock_->back_timestamp()) {
wall_clock_times->push_back(
- last_render_time_ +
- audio_clock_->TimeUntilPlayback(media_timestamp));
+ last_render_time_ + audio_clock_->TimeUntilPlayback(media_timestamp));
continue;
}
+ base::TimeDelta base_timestamp, time_until_playback;
+ if (media_timestamp < audio_clock_->front_timestamp()) {
+ base_timestamp = audio_clock_->front_timestamp();
+ time_until_playback = time_until_front;
+ } else {
+ base_timestamp = audio_clock_->back_timestamp();
+ time_until_playback = time_until_back;
+ }
+
// In practice, most calls will be estimates given the relatively small
// window in which clients can get the actual time.
- wall_clock_times->push_back(
- last_render_time_ + audio_clock_->TimeUntilPlayback(base_time) +
- base::TimeDelta::FromMicroseconds(
- (media_timestamp - base_time).InMicroseconds() / playback_rate_));
+ wall_clock_times->push_back(last_render_time_ + time_until_playback +
+ (media_timestamp - base_timestamp) /
+ playback_rate);
}
- return true;
+
+ return is_time_moving;
}
TimeSource* AudioRendererImpl::GetTimeSource() {
@@ -596,10 +619,11 @@ int AudioRendererImpl::Render(AudioBus* audio_bus,
int frames_written = 0;
{
base::AutoLock auto_lock(lock_);
- last_render_time_ = base::TimeTicks::Now();
+ last_render_time_ = tick_clock_->NowTicks();
if (!stop_rendering_time_.is_null()) {
- // TODO(dalecurtis): Use |stop_rendering_time_| to advance the AudioClock.
+ audio_clock_->CompensateForSuspendedWrites(
+ last_render_time_ - stop_rendering_time_, delay_frames);
stop_rendering_time_ = base::TimeTicks();
}
diff --git a/media/renderers/audio_renderer_impl.h b/media/renderers/audio_renderer_impl.h
index 43d12ea..944d8ed 100644
--- a/media/renderers/audio_renderer_impl.h
+++ b/media/renderers/audio_renderer_impl.h
@@ -36,6 +36,7 @@
namespace base {
class SingleThreadTaskRunner;
+class TickClock;
}
namespace media {
@@ -217,6 +218,9 @@ class MEDIA_EXPORT AudioRendererImpl
// Callback provided to Flush().
base::Closure flush_cb_;
+ // Overridable tick clock for testing.
+ scoped_ptr<base::TickClock> tick_clock_;
+
// After Initialize() has completed, all variables below must be accessed
// under |lock_|. ------------------------------------------------------------
base::Lock lock_;
@@ -257,7 +261,8 @@ class MEDIA_EXPORT AudioRendererImpl
base::TimeTicks last_render_time_;
// Set to the value of |last_render_time_| when StopRendering_Locked() is
- // called for any reason. Cleared by the next successful Render() call.
+ // called for any reason. Cleared by the next successful Render() call after
+ // being used to adjust for lost time between the last call.
base::TimeTicks stop_rendering_time_;
// Set upon receipt of the first decoded buffer after a StartPlayingFrom().
diff --git a/media/renderers/audio_renderer_impl_unittest.cc b/media/renderers/audio_renderer_impl_unittest.cc
index e65dd4b..6a76435 100644
--- a/media/renderers/audio_renderer_impl_unittest.cc
+++ b/media/renderers/audio_renderer_impl_unittest.cc
@@ -7,6 +7,7 @@
#include "base/format_macros.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
+#include "base/test/simple_test_tick_clock.h"
#include "media/base/audio_buffer_converter.h"
#include "media/base/audio_hardware_config.h"
#include "media/base/audio_splicer.h"
@@ -60,6 +61,7 @@ class AudioRendererImplTest : public ::testing::Test {
// Give the decoder some non-garbage media properties.
AudioRendererImplTest()
: hardware_config_(AudioParameters(), AudioParameters()),
+ tick_clock_(new base::SimpleTestTickClock()),
demuxer_stream_(DemuxerStream::AUDIO),
decoder_(new MockAudioDecoder()),
ended_(false) {
@@ -98,6 +100,8 @@ class AudioRendererImplTest : public ::testing::Test {
decoders.Pass(),
hardware_config_,
new MediaLog()));
+ renderer_->tick_clock_.reset(tick_clock_);
+ tick_clock_->Advance(base::TimeDelta::FromSeconds(1));
}
virtual ~AudioRendererImplTest() {
@@ -285,14 +289,34 @@ class AudioRendererImplTest : public ::testing::Test {
// Attempts to consume |requested_frames| frames from |renderer_|'s internal
// buffer. Returns true if and only if all of |requested_frames| were able
// to be consumed.
- bool ConsumeBufferedData(OutputFrames requested_frames) {
+ bool ConsumeBufferedData(OutputFrames requested_frames,
+ base::TimeDelta delay) {
scoped_ptr<AudioBus> bus =
AudioBus::Create(kChannels, requested_frames.value);
int frames_read = 0;
- EXPECT_TRUE(sink_->Render(bus.get(), 0, &frames_read));
+ EXPECT_TRUE(sink_->Render(bus.get(), delay.InMilliseconds(), &frames_read));
return frames_read == requested_frames.value;
}
+ bool ConsumeBufferedData(OutputFrames requested_frames) {
+ return ConsumeBufferedData(requested_frames, base::TimeDelta());
+ }
+
+ base::TimeTicks ConvertMediaTime(base::TimeDelta timestamp,
+ bool* is_time_moving) {
+ std::vector<base::TimeTicks> wall_clock_times;
+ *is_time_moving = renderer_->GetWallClockTimes(
+ std::vector<base::TimeDelta>(1, timestamp), &wall_clock_times);
+ return wall_clock_times[0];
+ }
+
+ base::TimeTicks CurrentMediaWallClockTime(bool* is_time_moving) {
+ std::vector<base::TimeTicks> wall_clock_times;
+ *is_time_moving = renderer_->GetWallClockTimes(
+ std::vector<base::TimeDelta>(), &wall_clock_times);
+ return wall_clock_times[0];
+ }
+
OutputFrames frames_buffered() {
return OutputFrames(renderer_->algorithm_->frames_buffered());
}
@@ -334,6 +358,7 @@ class AudioRendererImplTest : public ::testing::Test {
scoped_ptr<AudioRendererImpl> renderer_;
scoped_refptr<FakeAudioRendererSink> sink_;
AudioHardwareConfig hardware_config_;
+ base::SimpleTestTickClock* tick_clock_;
private:
void DecodeDecoder(const scoped_refptr<DecoderBuffer>& buffer,
@@ -516,10 +541,6 @@ TEST_F(AudioRendererImplTest, Underflow_Flush) {
WaitForPendingRead();
StopTicking();
- // After time stops ticking wall clock times should not be returned.
- EXPECT_FALSE(
- renderer_->GetWallClockTimes(std::vector<base::TimeDelta>(1), nullptr));
-
// We shouldn't expect another buffering state change when flushing.
FlushDuringPendingRead();
}
@@ -749,4 +770,115 @@ TEST_F(AudioRendererImplTest, SetPlaybackRate) {
EXPECT_EQ(FakeAudioRendererSink::kPlaying, sink_->state());
}
+TEST_F(AudioRendererImplTest, TimeSourceBehavior) {
+ Initialize();
+ Preroll();
+
+ AudioTimestampHelper timestamp_helper(kOutputSamplesPerSecond);
+ timestamp_helper.SetBaseTimestamp(base::TimeDelta());
+
+ // Prior to start, time should be shown as not moving.
+ bool is_time_moving = false;
+ EXPECT_EQ(base::TimeTicks(),
+ ConvertMediaTime(base::TimeDelta(), &is_time_moving));
+ EXPECT_FALSE(is_time_moving);
+
+ EXPECT_EQ(base::TimeTicks(), CurrentMediaWallClockTime(&is_time_moving));
+ EXPECT_FALSE(is_time_moving);
+
+ // Start ticking, but use a zero playback rate, time should still be stopped
+ // until a positive playback rate is set and the first Render() is called.
+ renderer_->SetPlaybackRate(0.0);
+ StartTicking();
+ EXPECT_EQ(base::TimeTicks(), CurrentMediaWallClockTime(&is_time_moving));
+ EXPECT_FALSE(is_time_moving);
+ renderer_->SetPlaybackRate(1.0);
+ EXPECT_EQ(base::TimeTicks(), CurrentMediaWallClockTime(&is_time_moving));
+ EXPECT_FALSE(is_time_moving);
+ renderer_->SetPlaybackRate(1.0);
+
+ // Issue the first render call to start time moving.
+ OutputFrames frames_to_consume(frames_buffered().value / 2);
+ EXPECT_TRUE(ConsumeBufferedData(frames_to_consume));
+ WaitForPendingRead();
+
+ // Time shouldn't change just yet because we've only sent the initial audio
+ // data to the hardware.
+ EXPECT_EQ(tick_clock_->NowTicks(),
+ ConvertMediaTime(base::TimeDelta(), &is_time_moving));
+ EXPECT_TRUE(is_time_moving);
+
+ // Consume some more audio data.
+ frames_to_consume = frames_buffered();
+ tick_clock_->Advance(
+ base::TimeDelta::FromSecondsD(1.0 / kOutputSamplesPerSecond));
+ EXPECT_TRUE(ConsumeBufferedData(frames_to_consume));
+
+ // Time should change now that the audio hardware has called back.
+ const base::TimeTicks wall_clock_time_zero =
+ tick_clock_->NowTicks() -
+ timestamp_helper.GetFrameDuration(frames_to_consume.value);
+ EXPECT_EQ(wall_clock_time_zero,
+ ConvertMediaTime(base::TimeDelta(), &is_time_moving));
+ EXPECT_TRUE(is_time_moving);
+
+ // The current wall clock time should change as our tick clock advances, up
+ // until we've reached the end of played out frames.
+ const int kSteps = 4;
+ const base::TimeDelta kAdvanceDelta =
+ timestamp_helper.GetFrameDuration(frames_to_consume.value) / kSteps;
+
+ for (int i = 0; i < kSteps; ++i) {
+ tick_clock_->Advance(kAdvanceDelta);
+ EXPECT_EQ(tick_clock_->NowTicks(),
+ CurrentMediaWallClockTime(&is_time_moving));
+ EXPECT_TRUE(is_time_moving);
+ }
+
+ // Converting the current media time should be relative to wall clock zero.
+ EXPECT_EQ(wall_clock_time_zero + kSteps * kAdvanceDelta,
+ ConvertMediaTime(renderer_->CurrentMediaTime(), &is_time_moving));
+ EXPECT_TRUE(is_time_moving);
+
+ // Advancing once more will exceed the amount of played out frames finally.
+ base::TimeTicks current_time = tick_clock_->NowTicks();
+ tick_clock_->Advance(
+ base::TimeDelta::FromSecondsD(1.0 / kOutputSamplesPerSecond));
+ EXPECT_EQ(current_time, CurrentMediaWallClockTime(&is_time_moving));
+ EXPECT_TRUE(is_time_moving);
+
+ StopTicking();
+ DeliverRemainingAudio();
+
+ // Elapse a lot of time between StopTicking() and the next Render() call.
+ const base::TimeDelta kOneSecond = base::TimeDelta::FromSeconds(1);
+ tick_clock_->Advance(kOneSecond);
+ StartTicking();
+
+ // Time should be stopped until the next render call.
+ EXPECT_EQ(current_time, CurrentMediaWallClockTime(&is_time_moving));
+ EXPECT_FALSE(is_time_moving);
+
+ // Consume some buffered data with a small delay.
+ base::TimeDelta delay_time = base::TimeDelta::FromMilliseconds(50);
+ frames_to_consume.value = frames_buffered().value / 16;
+ EXPECT_TRUE(ConsumeBufferedData(frames_to_consume, delay_time));
+
+ // Verify time is adjusted for the current delay.
+ current_time = tick_clock_->NowTicks() + delay_time;
+ EXPECT_EQ(current_time, CurrentMediaWallClockTime(&is_time_moving));
+ EXPECT_TRUE(is_time_moving);
+ EXPECT_EQ(current_time,
+ ConvertMediaTime(renderer_->CurrentMediaTime(), &is_time_moving));
+ EXPECT_TRUE(is_time_moving);
+
+ // Advance far enough that we shouldn't be clamped to current time (tested
+ // already above).
+ tick_clock_->Advance(kOneSecond);
+ EXPECT_EQ(
+ current_time + timestamp_helper.GetFrameDuration(frames_to_consume.value),
+ CurrentMediaWallClockTime(&is_time_moving));
+ EXPECT_TRUE(is_time_moving);
+}
+
} // namespace media
diff --git a/media/renderers/video_renderer_impl.cc b/media/renderers/video_renderer_impl.cc
index 2bb4547..c89aa1d 100644
--- a/media/renderers/video_renderer_impl.cc
+++ b/media/renderers/video_renderer_impl.cc
@@ -504,9 +504,45 @@ void VideoRendererImpl::FrameReady(VideoFrameStream::Status status,
AddReadyFrame_Locked(frame);
}
+ // Background rendering updates may not be ticking fast enough by itself to
+ // remove expired frames, so give it a boost here by ensuring we don't exit
+ // the decoding cycle too early.
+ //
+ // Similarly, if we've paused for underflow, remove all frames which are
+ // before the current media time.
+ const bool have_nothing = buffering_state_ != BUFFERING_HAVE_ENOUGH;
+ const bool have_nothing_and_paused = have_nothing && !sink_started_;
+ if (was_background_rendering_ ||
+ (use_new_video_renderering_path_ && have_nothing_and_paused &&
+ drop_frames_)) {
+ base::TimeTicks expiry_time;
+ if (have_nothing_and_paused) {
+ // Use the current media wall clock time plus the frame duration since
+ // RemoveExpiredFrames() is expecting the end point of an interval (it
+ // will subtract from the given value).
+ std::vector<base::TimeTicks> current_time;
+ wall_clock_time_cb_.Run(std::vector<base::TimeDelta>(), &current_time);
+ expiry_time = current_time[0] + algorithm_->average_frame_duration();
+ } else {
+ expiry_time = tick_clock_->NowTicks();
+ }
+
+ // Prior to rendering the first frame, |have_nothing_and_paused| will be
+ // true, correspondingly the |expiry_time| will be null; in this case
+ // there's no reason to try and remove any frames.
+ if (!expiry_time.is_null()) {
+ const size_t removed_frames =
+ algorithm_->RemoveExpiredFrames(expiry_time);
+
+ // Frames removed during underflow should be counted as dropped.
+ if (have_nothing_and_paused && removed_frames)
+ frames_dropped_ += removed_frames;
+ }
+ }
+
// Signal buffering state if we've met our conditions for having enough
// data.
- if (buffering_state_ != BUFFERING_HAVE_ENOUGH && HaveEnoughData_Locked()) {
+ if (have_nothing && HaveEnoughData_Locked()) {
TransitionToHaveEnough_Locked();
if (use_new_video_renderering_path_ && !sink_started_ &&
!rendered_end_of_stream_) {
@@ -516,14 +552,6 @@ void VideoRendererImpl::FrameReady(VideoFrameStream::Status status,
}
}
- // Background rendering updates may not be ticking fast enough by itself to
- // remove expired frames, so give it a boost here by ensuring we don't exit
- // the decoding cycle too early.
- if (was_background_rendering_) {
- DCHECK(use_new_video_renderering_path_);
- algorithm_->RemoveExpiredFrames(tick_clock_->NowTicks());
- }
-
// Always request more decoded video if we have capacity. This serves two
// purposes:
// 1) Prerolling while paused
@@ -711,6 +739,7 @@ void VideoRendererImpl::StartSink() {
void VideoRendererImpl::StopSink() {
DCHECK(task_runner_->BelongsToCurrentThread());
sink_->Stop();
+ algorithm_->set_time_stopped();
sink_started_ = false;
was_background_rendering_ = false;
}