blob: 01ebb53c6fa29842c318f18391677a83b59105c3 [file] [log] [blame]
// Copyright (c) 2011 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 {
ACTION(OnStop) {
arg0.Run();
}
// Mocked subclass of VideoRendererBase for testing purposes.
class MockVideoRendererBase : public VideoRendererBase {
public:
MockVideoRendererBase() {}
virtual ~MockVideoRendererBase() {}
// VideoRendererBase implementation.
MOCK_METHOD1(OnInitialize, bool(VideoDecoder* decoder));
MOCK_METHOD1(OnStop, void(const base::Closure& callback));
MOCK_METHOD0(OnFrameAvailable, void());
// Used for verifying check points during tests.
MOCK_METHOD1(CheckPoint, void(int id));
private:
DISALLOW_COPY_AND_ASSIGN(MockVideoRendererBase);
};
class VideoRendererBaseTest : public ::testing::Test {
public:
VideoRendererBaseTest()
: renderer_(new MockVideoRendererBase()),
decoder_(new MockVideoDecoder()),
cv_(&lock_),
event_(false, false),
seeking_(false) {
renderer_->set_host(&host_);
EXPECT_CALL(*decoder_, natural_size())
.WillRepeatedly(ReturnRef(kNaturalSize));
EXPECT_CALL(stats_callback_object_, OnStatistics(_))
.Times(AnyNumber());
}
virtual ~VideoRendererBaseTest() {
read_queue_.clear();
if (renderer_) {
Stop();
}
}
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()));
// Expects the video renderer to get duration from the host.
EXPECT_CALL(host_, GetDuration())
.WillRepeatedly(Return(base::TimeDelta()));
// 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));
// Then our subclass will be asked to initialize.
EXPECT_CALL(*renderer_, OnInitialize(_))
.WillOnce(Return(true));
// Initialize, we shouldn't have any reads.
renderer_->Initialize(decoder_,
NewExpectedClosure(), NewStatisticsCallback());
// Now seek to trigger prerolling.
Seek(0);
}
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();
}
void Seek(int64 timestamp) {
SCOPED_TRACE(base::StringPrintf("Seek(%" PRId64 ")", timestamp));
StartSeeking(timestamp);
FinishSeeking();
}
void Pause() {
SCOPED_TRACE("Pause()");
renderer_->Pause(NewWaitableClosure());
WaitForClosure();
}
void Flush() {
SCOPED_TRACE("Flush()");
renderer_->Flush(NewWaitableClosure());
WaitForClosure();
}
void Stop() {
SCOPED_TRACE("Stop()");
// Expect a call into the subclass.
EXPECT_CALL(*renderer_, OnStop(_))
.WillOnce(DoAll(OnStop(), Return()))
.RetiresOnSaturation();
renderer_->Stop(NewWaitableClosure());
WaitForClosure();
}
void Shutdown() {
Pause();
Flush();
Stop();
}
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() {
base::TimeDelta timeout =
base::TimeDelta::FromMilliseconds(TestTimeouts::action_timeout_ms());
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;
}
protected:
static const gfx::Size kNaturalSize;
static const int64 kDuration;
StatisticsCallback NewStatisticsCallback() {
return base::Bind(&MockStatisticsCallback::OnStatistics,
base::Unretained(&stats_callback_object_));
}
// Fixture members.
scoped_refptr<MockVideoRendererBase> renderer_;
scoped_refptr<MockVideoDecoder> decoder_;
StrictMock<MockFilterHost> host_;
MockStatisticsCallback stats_callback_object_;
// Receives all the buffers that renderer had provided to |decoder_|.
std::deque<scoped_refptr<VideoFrame> > read_queue_;
private:
// Called by VideoRendererBase when it wants a frame.
void FrameRequested(const VideoDecoder::ReadCB& callback) {
base::AutoLock l(lock_);
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() {
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_);
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_);
read_cb.Run(CreateFrame(i * kDuration, kDuration));
++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());
}
base::Lock lock_;
base::ConditionVariable cv_;
base::WaitableEvent event_;
// Used in conjunction with |lock_| and |cv_| for satisfying reads.
bool seeking_;
VideoDecoder::ReadCB read_cb_;
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;
// We expect the video size to be set.
EXPECT_CALL(host_, SetNaturalVideoSize(kNaturalSize));
// Our subclass will fail when asked to initialize.
EXPECT_CALL(*renderer_, OnInitialize(_))
.WillOnce(Return(false));
// We expect to receive an error.
EXPECT_CALL(host_, SetError(PIPELINE_ERROR_INITIALIZATION_FAILED));
// Initialize, we expect to have no reads.
renderer_->Initialize(decoder_,
NewExpectedClosure(), NewStatisticsCallback());
EXPECT_EQ(0u, read_queue_.size());
}
// Test successful initialization and preroll.
TEST_F(VideoRendererBaseTest, Initialize_Successful) {
Initialize();
ExpectCurrentTimestamp(0);
Shutdown();
}
TEST_F(VideoRendererBaseTest, Play) {
Initialize();
Play();
Shutdown();
}
TEST_F(VideoRendererBaseTest, Seek_Exact) {
Initialize();
Pause();
Flush();
Seek(kDuration * 6);
ExpectCurrentTimestamp(kDuration * 6);
Shutdown();
}
TEST_F(VideoRendererBaseTest, Seek_RightBefore) {
Initialize();
Pause();
Flush();
Seek(kDuration * 6 - 1);
ExpectCurrentTimestamp(kDuration * 5);
Shutdown();
}
TEST_F(VideoRendererBaseTest, Seek_RightAfter) {
Initialize();
Pause();
Flush();
Seek(kDuration * 6 + 1);
ExpectCurrentTimestamp(kDuration * 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();
}
TEST_F(VideoRendererBaseTest, GetCurrentFrame_Shutdown) {
Initialize();
Shutdown();
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();
}
} // namespace media