summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-12-21 21:15:58 +0000
committerscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-12-21 21:15:58 +0000
commit3cfcf068072a42ebf6b274dabe1967e2c3a1f825 (patch)
tree059d598abffb077f3877b9a9ba02631c3c89b16d /media
parent8f270be43ff51e35d4493e377c18bfca1c6b5be6 (diff)
downloadchromium_src-3cfcf068072a42ebf6b274dabe1967e2c3a1f825.zip
chromium_src-3cfcf068072a42ebf6b274dabe1967e2c3a1f825.tar.gz
chromium_src-3cfcf068072a42ebf6b274dabe1967e2c3a1f825.tar.bz2
Clean up VideoRendererBase timing code.
Tiny step towards simplifying VideoRendererBase code and respective subclasses. The biggest change was to replace if statements adjusting the value of |remaining_time| with explicit statements to sleep then retry the loop from the start. The nice part about doing this is that we guarantee we exercise all conditions in the scenario where a spurious signal to |frame_available_| causes the thread to wake up. BUG=28208 Review URL: http://codereview.chromium.org/8794001 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@115402 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/filters/video_renderer_base.cc198
-rw-r--r--media/filters/video_renderer_base.h10
-rw-r--r--media/filters/video_renderer_base_unittest.cc156
3 files changed, 225 insertions, 139 deletions
diff --git a/media/filters/video_renderer_base.cc b/media/filters/video_renderer_base.cc
index 7b40fbc..807594b 100644
--- a/media/filters/video_renderer_base.cc
+++ b/media/filters/video_renderer_base.cc
@@ -13,19 +13,6 @@
namespace media {
-// This equates to ~16.67 fps, which is just slow enough to be tolerable when
-// our video renderer is ahead of the audio playback.
-//
-// A higher value will be a slower frame rate, which looks worse but allows the
-// audio renderer to catch up faster. A lower value will be a smoother frame
-// rate, but results in the video being out of sync for longer.
-static const int64 kMaxSleepMilliseconds = 60;
-
-// The number of milliseconds to idle when we do not have anything to do.
-// Nothing special about the value, other than we're being more OS-friendly
-// than sleeping for 1 millisecond.
-static const int kIdleMilliseconds = 10;
-
VideoRendererBase::VideoRendererBase()
: frame_available_(&lock_),
state_(kUninitialized),
@@ -164,7 +151,15 @@ bool VideoRendererBase::HasEnded() {
// PlatformThread::Delegate implementation.
void VideoRendererBase::ThreadMain() {
base::PlatformThread::SetName("CrVideoRenderer");
- base::TimeDelta remaining_time;
+
+ // The number of milliseconds to idle when we do not have anything to do.
+ // Nothing special about the value, other than we're being more OS-friendly
+ // than sleeping for 1 millisecond.
+ //
+ // TOOD(scherkus): switch to pure event-driven frame timing instead of this
+ // kIdleTimeDelta business http://crbug.com/106874
+ const base::TimeDelta kIdleTimeDelta =
+ base::TimeDelta::FromMilliseconds(10);
uint32 frames_dropped = 0;
@@ -179,91 +174,83 @@ void VideoRendererBase::ThreadMain() {
base::AutoLock auto_lock(lock_);
- const base::TimeDelta kIdleTimeDelta =
- base::TimeDelta::FromMilliseconds(kIdleMilliseconds);
-
+ // Thread exit condition.
if (state_ == kStopped)
return;
+ // Remain idle as long as we're not playing.
if (state_ != kPlaying || playback_rate_ == 0) {
- remaining_time = kIdleTimeDelta;
- } else if (frames_queue_ready_.empty() ||
- frames_queue_ready_.front()->IsEndOfStream()) {
- if (current_frame_)
- remaining_time = CalculateSleepDuration(NULL, playback_rate_);
- else
- remaining_time = kIdleTimeDelta;
- } else {
- // Calculate how long until we should advance the frame, which is
- // typically negative but for playback rates < 1.0f may be long enough
- // that it makes more sense to idle and check again.
- scoped_refptr<VideoFrame> next_frame = frames_queue_ready_.front();
- remaining_time = CalculateSleepDuration(next_frame, playback_rate_);
+ frame_available_.TimedWait(kIdleTimeDelta);
+ continue;
}
- // TODO(jiesun): I do not think we should wake up every 10ms.
- // We should only wait up when following is true:
- // 1. frame arrival (use event);
- // 2. state_ change (use event);
- // 3. playback_rate_ change (use event);
- // 4. next frame's pts (use timeout);
- if (remaining_time > kIdleTimeDelta)
- remaining_time = kIdleTimeDelta;
-
- // We can not do anything about this until next frame arrival.
- // We do not want to spin in this case though.
- if (remaining_time.InMicroseconds() < 0 && frames_queue_ready_.empty())
- remaining_time = kIdleTimeDelta;
-
- if (remaining_time.InMicroseconds() > 0)
- frame_available_.TimedWait(remaining_time);
+ // Remain idle until we have the next frame ready for rendering.
+ if (ready_frames_.empty()) {
+ frame_available_.TimedWait(kIdleTimeDelta);
+ continue;
+ }
+
+ // Remain idle until we've initialized |current_frame_| via prerolling.
+ if (!current_frame_) {
+ // This can happen if our preroll only contains end of stream frames.
+ if (ready_frames_.front()->IsEndOfStream()) {
+ state_ = kEnded;
+ host()->NotifyEnded();
- if (state_ != kPlaying || playback_rate_ == 0)
+ // No need to sleep here as we idle when |state_ != kPlaying|.
+ continue;
+ }
+
+ frame_available_.TimedWait(kIdleTimeDelta);
continue;
+ }
+
+ // Calculate how long until we should advance the frame, which is
+ // typically negative but for playback rates < 1.0f may be long enough
+ // that it makes more sense to idle and check again.
+ base::TimeDelta remaining_time =
+ CalculateSleepDuration(ready_frames_.front(), playback_rate_);
- // Otherwise we're playing, so advance the frame and keep reading from the
- // decoder when following condition is satisfied:
- // 1. We had at least one backup frame.
- // 2. We had not reached end of stream.
- // 3. Current frame is out-dated.
- if (frames_queue_ready_.empty())
+ // Sleep up to a maximum of our idle time until we're within the time to
+ // render the next frame.
+ if (remaining_time.InMicroseconds() > 0) {
+ remaining_time = std::min(remaining_time, kIdleTimeDelta);
+ frame_available_.TimedWait(remaining_time);
continue;
+ }
+
- scoped_refptr<VideoFrame> next_frame = frames_queue_ready_.front();
- if (next_frame->IsEndOfStream()) {
+ // We're almost there!
+ //
+ // At this point we've rendered |current_frame_| for the proper amount
+ // of time and also have the next frame that ready for rendering.
+
+
+ // If the next frame is end of stream then we are truly at the end of the
+ // video stream.
+ //
+ // TODO(scherkus): deduplicate this end of stream check after we get rid of
+ // |current_frame_|.
+ if (ready_frames_.front()->IsEndOfStream()) {
state_ = kEnded;
- DVLOG(1) << "Video render gets EOS";
host()->NotifyEnded();
- continue;
- }
- // Normally we're ready to loop again at this point, but there are
- // exceptions that cause us to drop a frame and/or consider painting a
- // "next" frame.
- if (next_frame->GetTimestamp() > host()->GetTime() + kIdleTimeDelta &&
- current_frame_ &&
- current_frame_->GetTimestamp() <= host()->GetDuration()) {
+ // No need to sleep here as we idle when |state_ != kPlaying|.
continue;
}
- // If we got here then:
- // 1. next frame's timestamp is already current; or
- // 2. we do not have a current frame yet; or
- // 3. a special case when the stream is badly formatted and
- // we got a frame with timestamp greater than overall duration.
- // In this case we should proceed anyway and try to obtain the
- // end-of-stream packet.
-
+ // We cannot update |current_frame_| until we've completed the pending
+ // paint. Furthermore, the pending paint might be really slow: check to
+ // see if we have any ready frames that we can drop if they've already
+ // expired.
if (pending_paint_) {
- // The pending paint might be really slow. Check if we have any frames
- // available that we can drop if they've already expired.
- while (!frames_queue_ready_.empty()) {
+ while (!ready_frames_.empty()) {
// Can't drop anything if we're at the end.
- if (frames_queue_ready_.front()->IsEndOfStream())
+ if (ready_frames_.front()->IsEndOfStream())
break;
base::TimeDelta remaining_time =
- frames_queue_ready_.front()->GetTimestamp() - host()->GetTime();
+ ready_frames_.front()->GetTimestamp() - host()->GetTime();
// Still a chance we can render the frame!
if (remaining_time.InMicroseconds() > 0)
@@ -271,21 +258,23 @@ void VideoRendererBase::ThreadMain() {
// Frame dropped: read again.
++frames_dropped;
- frames_queue_ready_.pop_front();
+ ready_frames_.pop_front();
AttemptRead_Locked();
}
// Continue waiting for the current paint to finish.
+ frame_available_.TimedWait(kIdleTimeDelta);
continue;
}
+
// Congratulations! You've made it past the video frame timing gauntlet.
//
// We can now safely update the current frame, request another frame, and
// signal to the client that a new frame is available.
DCHECK(!pending_paint_);
- DCHECK(!frames_queue_ready_.empty());
- current_frame_ = frames_queue_ready_.front();
- frames_queue_ready_.pop_front();
+ DCHECK(!ready_frames_.empty());
+ current_frame_ = ready_frames_.front();
+ ready_frames_.pop_front();
AttemptRead_Locked();
base::AutoUnlock auto_unlock(lock_);
@@ -371,9 +360,18 @@ void VideoRendererBase::FrameReady(scoped_refptr<VideoFrame> frame) {
return;
}
+ // Adjust the incoming frame if it's rendering stop time is past the duration
+ // of the video itself. This is typically the last frame of the video and
+ // occurs if the container specifies a duration that isn't a multiple of the
+ // frame rate.
+ if (!frame->IsEndOfStream() &&
+ (frame->GetTimestamp() + frame->GetDuration()) > host()->GetDuration()) {
+ frame->SetDuration(host()->GetDuration() - frame->GetTimestamp());
+ }
+
// This one's a keeper! Place it in the ready queue.
- frames_queue_ready_.push_back(frame);
- DCHECK_LE(frames_queue_ready_.size(),
+ ready_frames_.push_back(frame);
+ DCHECK_LE(ready_frames_.size(),
static_cast<size_t>(limits::kMaxVideoFrames));
frame_available_.Signal();
@@ -385,7 +383,7 @@ void VideoRendererBase::FrameReady(scoped_refptr<VideoFrame> frame) {
// purposes:
// 1) Prerolling while paused
// 2) Keeps decoding going if video rendering thread starts falling behind
- if (frames_queue_ready_.size() < limits::kMaxVideoFrames &&
+ if (ready_frames_.size() < limits::kMaxVideoFrames &&
!frame->IsEndOfStream()) {
AttemptRead_Locked();
return;
@@ -394,14 +392,15 @@ void VideoRendererBase::FrameReady(scoped_refptr<VideoFrame> frame) {
// If we're at capacity or end of stream while seeking we need to transition
// to prerolled.
if (state_ == kSeeking) {
+ DCHECK(!current_frame_);
state_ = kPrerolled;
// Because we might remain in the prerolled state for an undetermined amount
// of time (i.e., we were not playing before we received a seek), we'll
// manually update the current frame and notify the subclass below.
- if (!frames_queue_ready_.front()->IsEndOfStream()) {
- current_frame_ = frames_queue_ready_.front();
- frames_queue_ready_.pop_front();
+ if (!ready_frames_.front()->IsEndOfStream()) {
+ current_frame_ = ready_frames_.front();
+ ready_frames_.pop_front();
}
// ...and we're done seeking!
@@ -417,9 +416,8 @@ void VideoRendererBase::AttemptRead_Locked() {
lock_.AssertAcquired();
DCHECK_NE(kEnded, state_);
- if (pending_read_ || frames_queue_ready_.size() == limits::kMaxVideoFrames) {
+ if (pending_read_ || ready_frames_.size() == limits::kMaxVideoFrames)
return;
- }
pending_read_ = true;
decoder_->Read(read_cb_);
@@ -430,8 +428,8 @@ void VideoRendererBase::AttemptFlush_Locked() {
DCHECK_EQ(kFlushing, state_);
// Get rid of any ready frames.
- while (!frames_queue_ready_.empty()) {
- frames_queue_ready_.pop_front();
+ while (!ready_frames_.empty()) {
+ ready_frames_.pop_front();
}
if (!pending_paint_ && !pending_read_) {
@@ -442,30 +440,20 @@ void VideoRendererBase::AttemptFlush_Locked() {
}
base::TimeDelta VideoRendererBase::CalculateSleepDuration(
- VideoFrame* next_frame, float playback_rate) {
+ const scoped_refptr<VideoFrame>& next_frame,
+ float playback_rate) {
// Determine the current and next presentation timestamps.
base::TimeDelta now = host()->GetTime();
base::TimeDelta this_pts = current_frame_->GetTimestamp();
base::TimeDelta next_pts;
- if (next_frame) {
+ if (!next_frame->IsEndOfStream()) {
next_pts = next_frame->GetTimestamp();
} else {
next_pts = this_pts + current_frame_->GetDuration();
}
- // Determine our sleep duration based on whether time advanced.
- base::TimeDelta sleep;
- if (now == previous_time_) {
- // Time has not changed, assume we sleep for the frame's duration.
- sleep = next_pts - this_pts;
- } else {
- // Time has changed, figure out real sleep duration.
- sleep = next_pts - now;
- previous_time_ = now;
- }
-
// Scale our sleep based on the playback rate.
- // TODO(scherkus): floating point badness and degrade gracefully.
+ base::TimeDelta sleep = next_pts - now;
return base::TimeDelta::FromMicroseconds(
static_cast<int64>(sleep.InMicroseconds() / playback_rate));
}
diff --git a/media/filters/video_renderer_base.h b/media/filters/video_renderer_base.h
index 47c3893..7891125 100644
--- a/media/filters/video_renderer_base.h
+++ b/media/filters/video_renderer_base.h
@@ -103,8 +103,9 @@ class MEDIA_EXPORT VideoRendererBase
// the next frame timestamp (may be NULL), and the provided playback rate.
//
// We don't use |playback_rate_| to avoid locking.
- base::TimeDelta CalculateSleepDuration(VideoFrame* next_frame,
- float playback_rate);
+ base::TimeDelta CalculateSleepDuration(
+ const scoped_refptr<VideoFrame>& next_frame,
+ float playback_rate);
// Safely handles entering to an error state.
void EnterErrorState_Locked(PipelineStatus status);
@@ -120,7 +121,7 @@ class MEDIA_EXPORT VideoRendererBase
// Queue of incoming frames as well as the current frame since the last time
// OnFrameAvailable() was called.
typedef std::deque<scoped_refptr<VideoFrame> > VideoFrameQueue;
- VideoFrameQueue frames_queue_ready_;
+ VideoFrameQueue ready_frames_;
// The current frame available to subclasses for rendering via
// GetCurrentFrame(). |current_frame_| can only be altered when
@@ -180,9 +181,6 @@ class MEDIA_EXPORT VideoRendererBase
// Video thread handle.
base::PlatformThreadHandle thread_;
- // Previous time returned from the pipeline.
- base::TimeDelta previous_time_;
-
// Keep track of various pending operations:
// - |pending_read_| is true when there's an active video decoding request.
// - |pending_paint_| is true when |current_frame_| is currently being
diff --git a/media/filters/video_renderer_base_unittest.cc b/media/filters/video_renderer_base_unittest.cc
index 2c8a661..552453c 100644
--- a/media/filters/video_renderer_base_unittest.cc
+++ b/media/filters/video_renderer_base_unittest.cc
@@ -31,6 +31,11 @@ using ::testing::StrictMock;
namespace media {
+static const int64 kFrameDuration = 10;
+static const int64 kVideoDuration = kFrameDuration * 100;
+static const int64 kEndOfStream = kint64min;
+static const gfx::Size kNaturalSize(16u, 16u);
+
ACTION(OnStop) {
arg0.Run();
}
@@ -60,6 +65,8 @@ class VideoRendererBaseTest : public ::testing::Test {
decoder_(new MockVideoDecoder()),
cv_(&lock_),
event_(false, false),
+ timeout_(base::TimeDelta::FromMilliseconds(
+ TestTimeouts::action_timeout_ms())),
seeking_(false) {
renderer_->set_host(&host_);
@@ -79,15 +86,15 @@ class VideoRendererBaseTest : public ::testing::Test {
}
void Initialize() {
- // Who knows how many times ThreadMain() will execute!
- //
// TODO(scherkus): really, really, really need to inject a thread into
// VideoRendererBase... it makes mocking much harder.
- EXPECT_CALL(host_, GetTime()).WillRepeatedly(Return(base::TimeDelta()));
+ EXPECT_CALL(host_, GetTime())
+ .WillRepeatedly(Invoke(this, &VideoRendererBaseTest::GetTime));
// Expects the video renderer to get duration from the host.
EXPECT_CALL(host_, GetDuration())
- .WillRepeatedly(Return(base::TimeDelta()));
+ .WillRepeatedly(Return(
+ base::TimeDelta::FromMicroseconds(kVideoDuration)));
// Monitor reads from the decoder.
EXPECT_CALL(*decoder_, Read(_))
@@ -102,6 +109,9 @@ class VideoRendererBaseTest : public ::testing::Test {
EXPECT_CALL(*renderer_, OnInitialize(_))
.WillOnce(Return(true));
+ // Set playback rate before anything else happens.
+ renderer_->SetPlaybackRate(1.0f);
+
// Initialize, we shouldn't have any reads.
renderer_->Initialize(decoder_,
NewExpectedClosure(), NewStatisticsCallback());
@@ -127,10 +137,13 @@ class VideoRendererBaseTest : public ::testing::Test {
WaitForClosure();
}
+ // Seek and preroll to the given timestamp.
+ //
+ // Use |kEndOfStream| to preroll end of stream frames.
void Seek(int64 timestamp) {
SCOPED_TRACE(base::StringPrintf("Seek(%" PRId64 ")", timestamp));
StartSeeking(timestamp);
- FinishSeeking();
+ FinishSeeking(timestamp);
}
void Pause() {
@@ -163,6 +176,25 @@ class VideoRendererBaseTest : public ::testing::Test {
Stop();
}
+ // Delivers a frame with the given timestamp to the video renderer.
+ //
+ // Use |kEndOfStream| to pass in an end of stream frame.
+ void DeliverFrame(int64 timestamp) {
+ // Lock+swap to avoid re-entrancy issues.
+ VideoDecoder::ReadCB read_cb;
+ {
+ base::AutoLock l(lock_);
+ CHECK(!read_cb_.is_null()) << "Can't deliver a frame without a callback";
+ std::swap(read_cb, read_cb_);
+ }
+
+ if (timestamp == kEndOfStream) {
+ read_cb.Run(VideoFrame::CreateEmptyFrame());
+ } else {
+ read_cb.Run(CreateFrame(timestamp, kFrameDuration));
+ }
+ }
+
void ExpectCurrentFrame(bool present) {
scoped_refptr<VideoFrame> frame;
renderer_->GetCurrentFrame(&frame);
@@ -186,9 +218,7 @@ class VideoRendererBaseTest : public ::testing::Test {
}
void WaitForClosure() {
- base::TimeDelta timeout =
- base::TimeDelta::FromMilliseconds(TestTimeouts::action_timeout_ms());
- ASSERT_TRUE(event_.TimedWait(timeout));
+ ASSERT_TRUE(event_.TimedWait(timeout_));
event_.Reset();
}
@@ -202,10 +232,33 @@ class VideoRendererBaseTest : public ::testing::Test {
return frame;
}
- protected:
- static const gfx::Size kNaturalSize;
- static const int64 kDuration;
+ // Advances clock to |timestamp| and waits for the frame at |timestamp| to get
+ // rendered using |read_cb_| as the signal that the frame has rendered.
+ void RenderFrame(int64 timestamp) {
+ base::AutoLock l(lock_);
+ time_ = base::TimeDelta::FromMicroseconds(timestamp);
+ if (read_cb_.is_null()) {
+ cv_.TimedWait(timeout_);
+ CHECK(!read_cb_.is_null()) << "Timed out waiting for read to occur.";
+ }
+ }
+
+ // Advances clock to |timestamp| (which should be the timestamp of the last
+ // frame plus duration) and waits for the ended signal before returning.
+ void RenderLastFrame(int64 timestamp) {
+ EXPECT_CALL(host_, NotifyEnded())
+ .WillOnce(Invoke(&event_, &base::WaitableEvent::Signal));
+ {
+ base::AutoLock l(lock_);
+ time_ = base::TimeDelta::FromMicroseconds(timestamp);
+ }
+ CHECK(event_.TimedWait(timeout_)) << "Timed out waiting for ended signal.";
+ }
+
+ base::WaitableEvent* event() { return &event_; }
+ const base::TimeDelta& timeout() { return timeout_; }
+ protected:
StatisticsCallback NewStatisticsCallback() {
return base::Bind(&MockStatisticsCallback::OnStatistics,
base::Unretained(&stats_callback_object_));
@@ -221,6 +274,12 @@ class VideoRendererBaseTest : public ::testing::Test {
std::deque<scoped_refptr<VideoFrame> > read_queue_;
private:
+ // Called by VideoRendererBase for accessing the current time.
+ base::TimeDelta GetTime() {
+ base::AutoLock l(lock_);
+ return time_;
+ }
+
// Called by VideoRendererBase when it wants a frame.
void FrameRequested(const VideoDecoder::ReadCB& callback) {
base::AutoLock l(lock_);
@@ -237,18 +296,15 @@ class VideoRendererBaseTest : public ::testing::Test {
cv_.Signal();
}
- void FinishSeeking() {
+ void FinishSeeking(int64 timestamp) {
EXPECT_CALL(*renderer_, OnFrameAvailable());
EXPECT_TRUE(seeking_);
- base::TimeDelta timeout =
- base::TimeDelta::FromMilliseconds(TestTimeouts::action_timeout_ms());
-
// Satisfy the read requests. The callback must be executed in order
// to exit the loop since VideoRendererBase can read a few extra frames
// after |timestamp| in order to preroll.
- int64 i = 0;
base::AutoLock l(lock_);
+ int i = 0;
while (seeking_) {
if (!read_cb_.is_null()) {
VideoDecoder::ReadCB read_cb;
@@ -256,11 +312,15 @@ class VideoRendererBaseTest : public ::testing::Test {
// Unlock to deliver the frame to avoid re-entrancy issues.
base::AutoUnlock ul(lock_);
- read_cb.Run(CreateFrame(i * kDuration, kDuration));
- ++i;
+ if (timestamp == kEndOfStream) {
+ read_cb.Run(VideoFrame::CreateEmptyFrame());
+ } else {
+ read_cb.Run(CreateFrame(i * kFrameDuration, kFrameDuration));
+ i++;
+ }
} else {
// We want to wait iff we're still seeking but have no pending read.
- cv_.TimedWait(timeout);
+ cv_.TimedWait(timeout_);
CHECK(!seeking_ || !read_cb_.is_null())
<< "Timed out waiting for seek or read to occur.";
}
@@ -272,17 +332,16 @@ class VideoRendererBaseTest : public ::testing::Test {
base::Lock lock_;
base::ConditionVariable cv_;
base::WaitableEvent event_;
+ base::TimeDelta timeout_;
// Used in conjunction with |lock_| and |cv_| for satisfying reads.
bool seeking_;
VideoDecoder::ReadCB read_cb_;
+ base::TimeDelta time_;
DISALLOW_COPY_AND_ASSIGN(VideoRendererBaseTest);
};
-const gfx::Size VideoRendererBaseTest::kNaturalSize(16u, 16u);
-const int64 VideoRendererBaseTest::kDuration = 10;
-
// Test initialization where the subclass failed for some reason.
TEST_F(VideoRendererBaseTest, Initialize_Failed) {
InSequence s;
@@ -316,12 +375,34 @@ TEST_F(VideoRendererBaseTest, Play) {
Shutdown();
}
+TEST_F(VideoRendererBaseTest, EndOfStream) {
+ Initialize();
+ Play();
+
+ // Finish rendering up to the next-to-last frame, delivering end of stream
+ // frames as we go along.
+ //
+ // Put the gmock expectation here to avoid racing with the rendering thread.
+ EXPECT_CALL(*renderer_, OnFrameAvailable())
+ .Times(limits::kMaxVideoFrames - 1);
+ for (int i = 1; i < limits::kMaxVideoFrames; ++i) {
+ RenderFrame(kFrameDuration * i);
+ DeliverFrame(kEndOfStream);
+ }
+
+ // Finish rendering the last frame, we should NOT get a new frame but instead
+ // get notified of end of stream.
+ RenderLastFrame(kFrameDuration * limits::kMaxVideoFrames);
+
+ Shutdown();
+}
+
TEST_F(VideoRendererBaseTest, Seek_Exact) {
Initialize();
Pause();
Flush();
- Seek(kDuration * 6);
- ExpectCurrentTimestamp(kDuration * 6);
+ Seek(kFrameDuration * 6);
+ ExpectCurrentTimestamp(kFrameDuration * 6);
Shutdown();
}
@@ -329,8 +410,8 @@ TEST_F(VideoRendererBaseTest, Seek_RightBefore) {
Initialize();
Pause();
Flush();
- Seek(kDuration * 6 - 1);
- ExpectCurrentTimestamp(kDuration * 5);
+ Seek(kFrameDuration * 6 - 1);
+ ExpectCurrentTimestamp(kFrameDuration * 5);
Shutdown();
}
@@ -338,8 +419,8 @@ TEST_F(VideoRendererBaseTest, Seek_RightAfter) {
Initialize();
Pause();
Flush();
- Seek(kDuration * 6 + 1);
- ExpectCurrentTimestamp(kDuration * 6);
+ Seek(kFrameDuration * 6 + 1);
+ ExpectCurrentTimestamp(kFrameDuration * 6);
Shutdown();
}
@@ -373,6 +454,25 @@ TEST_F(VideoRendererBaseTest, GetCurrentFrame_Flushed) {
Shutdown();
}
+TEST_F(VideoRendererBaseTest, GetCurrentFrame_EndOfStream) {
+ Initialize();
+ Play();
+ Pause();
+ Flush();
+
+ // Seek and preroll only end of stream frames.
+ Seek(kEndOfStream);
+ ExpectCurrentFrame(false);
+
+ // Start playing, we should immediately get notified of end of stream.
+ EXPECT_CALL(host_, NotifyEnded())
+ .WillOnce(Invoke(event(), &base::WaitableEvent::Signal));
+ Play();
+ CHECK(event()->TimedWait(timeout())) << "Timed out waiting for ended signal.";
+
+ Shutdown();
+}
+
TEST_F(VideoRendererBaseTest, GetCurrentFrame_Shutdown) {
Initialize();
Shutdown();