blob: c5507a47df58334c5acb7662d5baad09820fdfd0 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/bind.h"
#include "base/callback.h"
#include "base/format_macros.h"
#include "base/stl_util.h"
#include "base/stringprintf.h"
#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/test_timeouts.h"
#include "media/base/data_buffer.h"
#include "media/base/limits.h"
#include "media/base/mock_callback.h"
#include "media/base/mock_filter_host.h"
#include "media/base/mock_filters.h"
#include "media/base/video_frame.h"
#include "media/filters/video_renderer_base.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::ReturnRef;
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();
}
class VideoRendererBaseTest : public ::testing::Test {
public:
VideoRendererBaseTest()
: decoder_(new MockVideoDecoder()),
cv_(&lock_),
event_(false, false),
timeout_(base::TimeDelta::FromMilliseconds(
TestTimeouts::action_timeout_ms())),
seeking_(false),
paint_cv_(&lock_),
paint_was_called_(false),
should_queue_read_cb_(false) {
renderer_ = new VideoRendererBase(
base::Bind(&VideoRendererBaseTest::Paint, base::Unretained(this)),
base::Bind(&VideoRendererBaseTest::SetOpaqueCBWasCalled,
base::Unretained(this)),
true);
renderer_->set_host(&host_);
EXPECT_CALL(*decoder_, natural_size())
.WillRepeatedly(ReturnRef(kNaturalSize));
EXPECT_CALL(statistics_cb_object_, OnStatistics(_))
.Times(AnyNumber());
EXPECT_CALL(*this, SetOpaqueCBWasCalled(_))
.WillRepeatedly(::testing::Return());
EXPECT_CALL(*decoder_, Stop(_))
.WillRepeatedly(OnStop());
EXPECT_CALL(*this, TimeCBWasCalled(_))
.WillRepeatedly(::testing::Return());
}
virtual ~VideoRendererBaseTest() {
read_queue_.clear();
if (renderer_) {
Stop();
}
}
MOCK_METHOD1(TimeCBWasCalled, void(base::TimeDelta));
MOCK_CONST_METHOD1(SetOpaqueCBWasCalled, void(bool));
void Initialize() {
// TODO(scherkus): really, really, really need to inject a thread into
// VideoRendererBase... it makes mocking much harder.
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::FromMicroseconds(kVideoDuration)));
// Monitor reads from the decoder.
EXPECT_CALL(*decoder_, Read(_))
.WillRepeatedly(Invoke(this, &VideoRendererBaseTest::FrameRequested));
InSequence s;
// We expect the video size to be set.
EXPECT_CALL(host_, SetNaturalVideoSize(kNaturalSize));
// Set playback rate before anything else happens.
renderer_->SetPlaybackRate(1.0f);
// Initialize, we shouldn't have any reads.
renderer_->Initialize(decoder_,
NewExpectedStatusCB(PIPELINE_OK),
NewStatisticsCB(),
NewTimeCB());
// Now seek to trigger prerolling.
Seek(0);
}
// Instead of immediately satisfying a decoder Read request, queue it up.
void QueueReadCB() {
should_queue_read_cb_ = true;
}
void SatisfyQueuedReadCB() {
base::AutoLock l(lock_);
CHECK(should_queue_read_cb_ && !queued_read_cb_.is_null());
should_queue_read_cb_ = false;
VideoDecoder::ReadCB read_cb(queued_read_cb_);
queued_read_cb_.Reset();
base::AutoUnlock u(lock_);
read_cb.Run(VideoFrame::CreateEmptyFrame());
}
void StartSeeking(int64 timestamp) {
EXPECT_FALSE(seeking_);
// Seek to trigger prerolling.
seeking_ = true;
renderer_->Seek(base::TimeDelta::FromMicroseconds(timestamp),
base::Bind(&VideoRendererBaseTest::OnSeekComplete,
base::Unretained(this),
PIPELINE_OK));
}
void Play() {
SCOPED_TRACE("Play()");
renderer_->Play(NewWaitableClosure());
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(timestamp);
}
void Pause() {
SCOPED_TRACE("Pause()");
renderer_->Pause(NewWaitableClosure());
WaitForClosure();
}
void Flush() {
SCOPED_TRACE("Flush()");
renderer_->Flush(NewWaitableClosure());
WaitForClosure();
}
void Stop() {
SCOPED_TRACE("Stop()");
renderer_->Stop(NewWaitableClosure());
WaitForClosure();
}
void Shutdown() {
Pause();
Flush();
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 AbortRead() {
// 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_);
}
read_cb.Run(NULL);
}
void ExpectCurrentFrame(bool present) {
scoped_refptr<VideoFrame> frame;
renderer_->GetCurrentFrame(&frame);
if (present) {
EXPECT_TRUE(frame);
} else {
EXPECT_FALSE(frame);
}
renderer_->PutCurrentFrame(frame);
}
void ExpectCurrentTimestamp(int64 timestamp) {
scoped_refptr<VideoFrame> frame;
renderer_->GetCurrentFrame(&frame);
EXPECT_EQ(timestamp, frame->GetTimestamp().InMicroseconds());
renderer_->PutCurrentFrame(frame);
}
base::Closure NewWaitableClosure() {
return base::Bind(&base::WaitableEvent::Signal, base::Unretained(&event_));
}
void WaitForClosure() {
ASSERT_TRUE(event_.TimedWait(timeout_));
event_.Reset();
}
// Creates a frame with given timestamp and duration.
scoped_refptr<VideoFrame> CreateFrame(int64 timestamp, int64 duration) {
scoped_refptr<VideoFrame> frame =
VideoFrame::CreateFrame(VideoFrame::RGB32, kNaturalSize.width(),
kNaturalSize.height(),
base::TimeDelta::FromMicroseconds(timestamp),
base::TimeDelta::FromMicroseconds(duration));
return frame;
}
// 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);
paint_was_called_ = false;
if (read_cb_.is_null()) {
cv_.TimedWait(timeout_);
CHECK(!read_cb_.is_null()) << "Timed out waiting for read to occur.";
}
WaitForPaint_Locked();
}
// 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_; }
void VerifyNotSeeking() {
base::AutoLock l(lock_);
ASSERT_FALSE(seeking_);
}
protected:
StatisticsCB NewStatisticsCB() {
return base::Bind(&MockStatisticsCB::OnStatistics,
base::Unretained(&statistics_cb_object_));
}
VideoRenderer::TimeCB NewTimeCB() {
return base::Bind(&VideoRendererBaseTest::TimeCBWasCalled,
base::Unretained(this));
}
// Fixture members.
scoped_refptr<VideoRendererBase> renderer_;
scoped_refptr<MockVideoDecoder> decoder_;
StrictMock<MockFilterHost> host_;
MockStatisticsCB statistics_cb_object_;
// Receives all the buffers that renderer had provided to |decoder_|.
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_);
if (should_queue_read_cb_) {
CHECK(queued_read_cb_.is_null());
queued_read_cb_ = callback;
return;
}
CHECK(read_cb_.is_null());
read_cb_ = callback;
cv_.Signal();
}
void OnSeekComplete(PipelineStatus expected_status, PipelineStatus status) {
base::AutoLock l(lock_);
EXPECT_EQ(status, expected_status);
EXPECT_TRUE(seeking_);
seeking_ = false;
cv_.Signal();
}
void FinishSeeking(int64 timestamp) {
// 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.
base::AutoLock l(lock_);
EXPECT_TRUE(seeking_);
paint_was_called_ = false;
int i = 0;
while (seeking_) {
if (!read_cb_.is_null()) {
VideoDecoder::ReadCB read_cb;
std::swap(read_cb, read_cb_);
// Unlock to deliver the frame to avoid re-entrancy issues.
base::AutoUnlock ul(lock_);
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_);
CHECK(!seeking_ || !read_cb_.is_null())
<< "Timed out waiting for seek or read to occur.";
}
}
EXPECT_TRUE(read_cb_.is_null());
WaitForPaint_Locked();
}
void Paint() {
base::AutoLock l(lock_);
paint_was_called_ = true;
paint_cv_.Signal();
}
void WaitForPaint_Locked() {
lock_.AssertAcquired();
if (paint_was_called_)
return;
paint_cv_.TimedWait(timeout_);
EXPECT_TRUE(paint_was_called_);
}
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_;
// Used in conjunction with |lock_| to wait for Paint() calls.
base::ConditionVariable paint_cv_;
bool paint_was_called_;
// Holding queue for Read callbacks for exercising delayed demux/decode.
bool should_queue_read_cb_;
VideoDecoder::ReadCB queued_read_cb_;
DISALLOW_COPY_AND_ASSIGN(VideoRendererBaseTest);
};
TEST_F(VideoRendererBaseTest, Initialize) {
Initialize();
ExpectCurrentTimestamp(0);
Shutdown();
}
TEST_F(VideoRendererBaseTest, Play) {
Initialize();
Play();
Shutdown();
}
TEST_F(VideoRendererBaseTest, EndOfStream) {
Initialize();
Play();
// Finish rendering up to the next-to-last frame.
for (int i = 1; i < limits::kMaxVideoFrames; ++i)
RenderFrame(kFrameDuration * i);
// Finish rendering the last frame, we should NOT get a new frame but instead
// get notified of end of stream.
DeliverFrame(kEndOfStream);
RenderLastFrame(kFrameDuration * limits::kMaxVideoFrames);
Shutdown();
}
TEST_F(VideoRendererBaseTest, Seek_Exact) {
Initialize();
Pause();
Flush();
Seek(kFrameDuration * 6);
ExpectCurrentTimestamp(kFrameDuration * 6);
Shutdown();
}
TEST_F(VideoRendererBaseTest, Seek_RightBefore) {
Initialize();
Pause();
Flush();
Seek(kFrameDuration * 6 - 1);
ExpectCurrentTimestamp(kFrameDuration * 5);
Shutdown();
}
TEST_F(VideoRendererBaseTest, Seek_RightAfter) {
Initialize();
Pause();
Flush();
Seek(kFrameDuration * 6 + 1);
ExpectCurrentTimestamp(kFrameDuration * 6);
Shutdown();
}
TEST_F(VideoRendererBaseTest, GetCurrentFrame_Initialized) {
Initialize();
ExpectCurrentFrame(true); // Due to prerolling.
Shutdown();
}
TEST_F(VideoRendererBaseTest, GetCurrentFrame_Playing) {
Initialize();
Play();
ExpectCurrentFrame(true);
Shutdown();
}
TEST_F(VideoRendererBaseTest, GetCurrentFrame_Paused) {
Initialize();
Play();
Pause();
ExpectCurrentFrame(true);
Shutdown();
}
TEST_F(VideoRendererBaseTest, GetCurrentFrame_Flushed) {
Initialize();
Play();
Pause();
Flush();
ExpectCurrentFrame(false);
Shutdown();
}
#if defined(OS_MACOSX)
// http://crbug.com/109405
#define MAYBE_GetCurrentFrame_EndOfStream DISABLED_GetCurrentFrame_EndOfStream
#else
#define MAYBE_GetCurrentFrame_EndOfStream GetCurrentFrame_EndOfStream
#endif
TEST_F(VideoRendererBaseTest, MAYBE_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();
ExpectCurrentFrame(false);
}
// Stop() is called immediately during an error.
TEST_F(VideoRendererBaseTest, GetCurrentFrame_Error) {
Initialize();
Stop();
ExpectCurrentFrame(false);
}
// Verify that shutdown can only proceed after we return the current frame.
TEST_F(VideoRendererBaseTest, Shutdown_DuringPaint) {
Initialize();
Play();
// Grab the frame.
scoped_refptr<VideoFrame> frame;
renderer_->GetCurrentFrame(&frame);
EXPECT_TRUE(frame);
Pause();
// Start flushing -- it won't complete until we return the frame.
renderer_->Flush(NewWaitableClosure());
// Return the frame and wait.
renderer_->PutCurrentFrame(frame);
WaitForClosure();
Stop();
}
// Verify that a late decoder response doesn't break invariants in the renderer.
TEST_F(VideoRendererBaseTest, StopDuringOutstandingRead) {
Initialize();
Pause();
Flush();
QueueReadCB();
StartSeeking(kFrameDuration * 6); // Force-decode some more.
renderer_->Stop(NewWaitableClosure());
SatisfyQueuedReadCB();
WaitForClosure(); // Finish the Stop().
}
TEST_F(VideoRendererBaseTest, AbortPendingRead_Playing) {
Initialize();
Play();
// Render a frame to trigger a Read().
RenderFrame(kFrameDuration);
AbortRead();
Pause();
Flush();
Seek(kFrameDuration * 6);
ExpectCurrentTimestamp(kFrameDuration * 6);
Shutdown();
}
TEST_F(VideoRendererBaseTest, AbortPendingRead_Flush) {
Initialize();
Play();
// Render a frame to trigger a Read().
RenderFrame(kFrameDuration);
Pause();
renderer_->Flush(NewWaitableClosure());
AbortRead();
WaitForClosure();
Shutdown();
}
TEST_F(VideoRendererBaseTest, AbortPendingRead_Seek) {
Initialize();
Pause();
Flush();
StartSeeking(kFrameDuration * 6);
AbortRead();
VerifyNotSeeking();
Shutdown();
}
} // namespace media