diff options
author | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-21 21:15:58 +0000 |
---|---|---|
committer | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-21 21:15:58 +0000 |
commit | 3cfcf068072a42ebf6b274dabe1967e2c3a1f825 (patch) | |
tree | 059d598abffb077f3877b9a9ba02631c3c89b16d /media | |
parent | 8f270be43ff51e35d4493e377c18bfca1c6b5be6 (diff) | |
download | chromium_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.cc | 198 | ||||
-rw-r--r-- | media/filters/video_renderer_base.h | 10 | ||||
-rw-r--r-- | media/filters/video_renderer_base_unittest.cc | 156 |
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(); |