blob: f252a2eaae91b876bce181e27f583c037cff12e5 [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/callback_helpers.h"
#include "base/debug/stack_trace.h"
#include "base/message_loop.h"
#include "base/stl_util.h"
#include "base/stringprintf.h"
#include "base/synchronization/lock.h"
#include "base/timer.h"
#include "media/base/data_buffer.h"
#include "media/base/gmock_callback_support.h"
#include "media/base/limits.h"
#include "media/base/mock_filters.h"
#include "media/base/test_helpers.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 int kFrameDurationInMs = 10;
static const int kVideoDurationInMs = kFrameDurationInMs * 100;
static const VideoFrame::Format kVideoFormat = VideoFrame::YV12;
static const gfx::Size kCodedSize(16u, 16u);
static const gfx::Rect kVisibleRect(16u, 16u);
static const gfx::Size kNaturalSize(16u, 16u);
class VideoRendererBaseTest : public ::testing::Test {
public:
VideoRendererBaseTest()
: decoder_(new MockVideoDecoder()),
demuxer_stream_(new MockDemuxerStream()),
video_config_(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kVideoFormat,
kCodedSize, kVisibleRect, kNaturalSize, NULL, 0, false) {
renderer_ = new VideoRendererBase(
message_loop_.message_loop_proxy(),
media::SetDecryptorReadyCB(),
base::Bind(&VideoRendererBaseTest::OnPaint, base::Unretained(this)),
base::Bind(&VideoRendererBaseTest::OnSetOpaque, base::Unretained(this)),
true);
#if defined(__LB_SHELL__) || defined(COBALT)
EXPECT_CALL(*demuxer_stream_, StreamWasEncrypted())
.WillRepeatedly(Return(false));
#endif
EXPECT_CALL(*demuxer_stream_, type())
.WillRepeatedly(Return(DemuxerStream::VIDEO));
EXPECT_CALL(*demuxer_stream_, video_decoder_config())
.WillRepeatedly(ReturnRef(video_config_));
// We expect these to be called but we don't care how/when.
EXPECT_CALL(*decoder_, Stop(_))
.WillRepeatedly(RunClosure<0>());
EXPECT_CALL(statistics_cb_object_, OnStatistics(_))
.Times(AnyNumber());
EXPECT_CALL(*this, OnTimeUpdate(_))
.Times(AnyNumber());
EXPECT_CALL(*this, OnPaint())
.Times(AnyNumber());
EXPECT_CALL(*this, OnSetOpaque(_))
.Times(AnyNumber());
}
virtual ~VideoRendererBaseTest() {}
// Callbacks passed into VideoRendererBase().
MOCK_CONST_METHOD0(OnPaint, void());
MOCK_CONST_METHOD1(OnSetOpaque, void(bool));
// Callbacks passed into Initialize().
MOCK_METHOD1(OnTimeUpdate, void(base::TimeDelta));
MOCK_METHOD1(OnNaturalSizeChanged, void(const gfx::Size&));
void Initialize() {
InitializeWithDuration(kVideoDurationInMs);
}
void InitializeWithDuration(int duration_ms) {
duration_ = base::TimeDelta::FromMilliseconds(duration_ms);
// Monitor reads from the decoder.
EXPECT_CALL(*decoder_, Read(_))
.WillRepeatedly(Invoke(this, &VideoRendererBaseTest::FrameRequested));
EXPECT_CALL(*decoder_, Reset(_))
.WillRepeatedly(Invoke(this, &VideoRendererBaseTest::FlushRequested));
InSequence s;
EXPECT_CALL(*decoder_, Initialize(_, _, _))
.WillOnce(RunCallback<1>(PIPELINE_OK));
// Set playback rate before anything else happens.
renderer_->SetPlaybackRate(1.0f);
// Initialize, we shouldn't have any reads.
InitializeRenderer(PIPELINE_OK);
// We expect the video size to be set.
EXPECT_CALL(*this, OnNaturalSizeChanged(kNaturalSize));
// Start prerolling.
QueuePrerollFrames(0);
Preroll(0, PIPELINE_OK);
}
void InitializeRenderer(PipelineStatus expected) {
SCOPED_TRACE(base::StringPrintf("InitializeRenderer(%d)", expected));
WaitableMessageLoopEvent event;
CallInitialize(event.GetPipelineStatusCB());
event.RunAndWaitForStatus(expected);
}
void CallInitialize(const PipelineStatusCB& status_cb) {
VideoRendererBase::VideoDecoderList decoders;
decoders.push_back(decoder_);
renderer_->Initialize(
demuxer_stream_, decoders, status_cb,
base::Bind(&MockStatisticsCB::OnStatistics,
base::Unretained(&statistics_cb_object_)),
base::Bind(&VideoRendererBaseTest::OnTimeUpdate,
base::Unretained(this)),
base::Bind(&VideoRendererBaseTest::OnNaturalSizeChanged,
base::Unretained(this)),
ended_event_.GetClosure(), error_event_.GetPipelineStatusCB(),
base::Bind(&VideoRendererBaseTest::GetTime, base::Unretained(this)),
base::Bind(&VideoRendererBaseTest::GetDuration,
base::Unretained(this)));
}
void Play() {
SCOPED_TRACE("Play()");
WaitableMessageLoopEvent event;
renderer_->Play(event.GetClosure());
event.RunAndWait();
}
void Preroll(int timestamp_ms, PipelineStatus expected) {
SCOPED_TRACE(base::StringPrintf("Preroll(%d, %d)", timestamp_ms, expected));
WaitableMessageLoopEvent event;
renderer_->Preroll(
base::TimeDelta::FromMilliseconds(timestamp_ms),
event.GetPipelineStatusCB());
event.RunAndWaitForStatus(expected);
}
void Pause() {
SCOPED_TRACE("Pause()");
WaitableMessageLoopEvent event;
renderer_->Pause(event.GetClosure());
event.RunAndWait();
}
void Flush() {
SCOPED_TRACE("Flush()");
WaitableMessageLoopEvent event;
renderer_->Flush(event.GetClosure());
event.RunAndWait();
}
void Stop() {
SCOPED_TRACE("Stop()");
WaitableMessageLoopEvent event;
renderer_->Stop(event.GetClosure());
event.RunAndWait();
}
void Shutdown() {
Pause();
Flush();
Stop();
}
// Queues a VideoFrame with |next_frame_timestamp_|.
void QueueNextFrame() {
DCHECK_EQ(&message_loop_, MessageLoop::current());
DCHECK_LT(next_frame_timestamp_.InMicroseconds(),
duration_.InMicroseconds());
scoped_refptr<VideoFrame> frame = VideoFrame::CreateFrame(
VideoFrame::RGB32, kNaturalSize, gfx::Rect(kNaturalSize), kNaturalSize,
next_frame_timestamp_);
decode_results_.push_back(std::make_pair(
VideoDecoder::kOk, frame));
next_frame_timestamp_ +=
base::TimeDelta::FromMilliseconds(kFrameDurationInMs);
}
void QueueEndOfStream() {
DCHECK_EQ(&message_loop_, MessageLoop::current());
decode_results_.push_back(std::make_pair(
VideoDecoder::kOk, VideoFrame::CreateEmptyFrame()));
}
void QueueDecodeError() {
DCHECK_EQ(&message_loop_, MessageLoop::current());
scoped_refptr<VideoFrame> null_frame;
decode_results_.push_back(std::make_pair(
VideoDecoder::kDecodeError, null_frame));
}
void QueueAbortedRead() {
DCHECK_EQ(&message_loop_, MessageLoop::current());
scoped_refptr<VideoFrame> null_frame;
decode_results_.push_back(std::make_pair(
VideoDecoder::kOk, null_frame));
}
void QueuePrerollFrames(int timestamp_ms) {
DCHECK_EQ(&message_loop_, MessageLoop::current());
next_frame_timestamp_ = base::TimeDelta();
base::TimeDelta timestamp = base::TimeDelta::FromMilliseconds(timestamp_ms);
while (next_frame_timestamp_ < timestamp) {
QueueNextFrame();
}
// Queue the frame at |timestamp| plus additional ones for prerolling.
for (int i = 0; i < limits::kMaxVideoFrames; ++i) {
QueueNextFrame();
}
}
scoped_refptr<VideoFrame> GetCurrentFrame() {
scoped_refptr<VideoFrame> frame;
renderer_->GetCurrentFrame(&frame);
renderer_->PutCurrentFrame(frame);
return frame;
}
int GetCurrentTimestampInMs() {
scoped_refptr<VideoFrame> frame = GetCurrentFrame();
if (!frame)
return -1;
return frame->GetTimestamp().InMilliseconds();
}
void WaitForError(PipelineStatus expected) {
SCOPED_TRACE(base::StringPrintf("WaitForError(%d)", expected));
error_event_.RunAndWaitForStatus(expected);
}
void WaitForEnded() {
SCOPED_TRACE("WaitForEnded()");
ended_event_.RunAndWait();
}
void WaitForPendingRead() {
SCOPED_TRACE("WaitForPendingRead()");
if (!read_cb_.is_null())
return;
DCHECK(pending_read_cb_.is_null());
WaitableMessageLoopEvent event;
pending_read_cb_ = event.GetClosure();
event.RunAndWait();
DCHECK(!read_cb_.is_null());
DCHECK(pending_read_cb_.is_null());
}
void SatisfyPendingRead() {
CHECK(!read_cb_.is_null());
CHECK(!decode_results_.empty());
base::Closure closure = base::Bind(
read_cb_, decode_results_.front().first,
decode_results_.front().second);
read_cb_.Reset();
decode_results_.pop_front();
message_loop_.PostTask(FROM_HERE, closure);
}
void AdvanceTimeInMs(int time_ms) {
DCHECK_EQ(&message_loop_, MessageLoop::current());
base::AutoLock l(lock_);
time_ += base::TimeDelta::FromMilliseconds(time_ms);
DCHECK_LE(time_.InMicroseconds(), duration_.InMicroseconds());
}
protected:
// Fixture members.
scoped_refptr<VideoRendererBase> renderer_;
scoped_refptr<MockVideoDecoder> decoder_;
scoped_refptr<MockDemuxerStream> demuxer_stream_;
MockStatisticsCB statistics_cb_object_;
private:
base::TimeDelta GetTime() {
base::AutoLock l(lock_);
return time_;
}
base::TimeDelta GetDuration() {
return duration_;
}
void FrameRequested(const VideoDecoder::ReadCB& read_cb) {
DCHECK_EQ(&message_loop_, MessageLoop::current());
CHECK(read_cb_.is_null());
read_cb_ = read_cb;
// Wake up WaitForPendingRead() if needed.
if (!pending_read_cb_.is_null())
base::ResetAndReturn(&pending_read_cb_).Run();
if (decode_results_.empty())
return;
SatisfyPendingRead();
}
void FlushRequested(const base::Closure& callback) {
DCHECK_EQ(&message_loop_, MessageLoop::current());
decode_results_.clear();
if (!read_cb_.is_null()) {
QueueAbortedRead();
SatisfyPendingRead();
}
message_loop_.PostTask(FROM_HERE, callback);
}
MessageLoop message_loop_;
VideoDecoderConfig video_config_;
// Used to protect |time_|.
base::Lock lock_;
base::TimeDelta time_;
// Used for satisfying reads.
VideoDecoder::ReadCB read_cb_;
base::TimeDelta next_frame_timestamp_;
base::TimeDelta duration_;
WaitableMessageLoopEvent error_event_;
WaitableMessageLoopEvent ended_event_;
base::Closure pending_read_cb_;
std::deque<std::pair<
VideoDecoder::Status, scoped_refptr<VideoFrame> > > decode_results_;
DISALLOW_COPY_AND_ASSIGN(VideoRendererBaseTest);
};
TEST_F(VideoRendererBaseTest, DoNothing) {
// Test that creation and deletion doesn't depend on calls to Initialize()
// and/or Stop().
}
TEST_F(VideoRendererBaseTest, StopWithoutInitialize) {
Stop();
}
TEST_F(VideoRendererBaseTest, Initialize) {
Initialize();
EXPECT_EQ(0, GetCurrentTimestampInMs());
Shutdown();
}
static void ExpectNotCalled(PipelineStatus) {
base::debug::StackTrace stack;
ADD_FAILURE() << "Expected callback not to be called\n" << stack.ToString();
}
TEST_F(VideoRendererBaseTest, StopWhileInitializing) {
EXPECT_CALL(*decoder_, Initialize(_, _, _))
.WillOnce(RunCallback<1>(PIPELINE_OK));
CallInitialize(base::Bind(&ExpectNotCalled));
Stop();
// ~VideoRendererBase() will CHECK() if we left anything initialized.
}
TEST_F(VideoRendererBaseTest, StopWhileFlushing) {
Initialize();
Pause();
renderer_->Flush(base::Bind(&ExpectNotCalled, PIPELINE_OK));
Stop();
// ~VideoRendererBase() will CHECK() if we left anything initialized.
}
TEST_F(VideoRendererBaseTest, Play) {
Initialize();
Play();
Shutdown();
}
TEST_F(VideoRendererBaseTest, EndOfStream_DefaultFrameDuration) {
Initialize();
Play();
// Verify that the ended callback fires when the default last frame duration
// has elapsed.
int end_timestamp = kFrameDurationInMs * limits::kMaxVideoFrames +
VideoRendererBase::kMaxLastFrameDuration().InMilliseconds();
EXPECT_LT(end_timestamp, kVideoDurationInMs);
QueueEndOfStream();
AdvanceTimeInMs(end_timestamp);
WaitForEnded();
Shutdown();
}
TEST_F(VideoRendererBaseTest, EndOfStream_ClipDuration) {
int duration = kVideoDurationInMs + kFrameDurationInMs / 2;
InitializeWithDuration(duration);
Play();
// Render all frames except for the last |limits::kMaxVideoFrames| frames
// and deliver all the frames between the start and |duration|. The preroll
// inside Initialize() makes this a little confusing, but |timestamp| is
// the current render time and QueueNextFrame() delivers a frame with a
// timestamp that is |timestamp| + limits::kMaxVideoFrames *
// kFrameDurationInMs.
int timestamp = kFrameDurationInMs;
int end_timestamp = duration - limits::kMaxVideoFrames * kFrameDurationInMs;
for (; timestamp < end_timestamp; timestamp += kFrameDurationInMs) {
QueueNextFrame();
}
// Queue the end of stream frame and wait for the last frame to be rendered.
QueueEndOfStream();
AdvanceTimeInMs(duration);
WaitForEnded();
Shutdown();
}
TEST_F(VideoRendererBaseTest, DecodeError_Playing) {
Initialize();
Play();
QueueDecodeError();
AdvanceTimeInMs(kVideoDurationInMs);
WaitForError(PIPELINE_ERROR_DECODE);
Shutdown();
}
TEST_F(VideoRendererBaseTest, DecodeError_DuringPreroll) {
Initialize();
Pause();
Flush();
QueueDecodeError();
Preroll(kFrameDurationInMs * 6, PIPELINE_ERROR_DECODE);
Shutdown();
}
TEST_F(VideoRendererBaseTest, Preroll_Exact) {
Initialize();
Pause();
Flush();
QueuePrerollFrames(kFrameDurationInMs * 6);
Preroll(kFrameDurationInMs * 6, PIPELINE_OK);
EXPECT_EQ(kFrameDurationInMs * 6, GetCurrentTimestampInMs());
Shutdown();
}
TEST_F(VideoRendererBaseTest, Preroll_RightBefore) {
Initialize();
Pause();
Flush();
QueuePrerollFrames(kFrameDurationInMs * 6);
Preroll(kFrameDurationInMs * 6 - 1, PIPELINE_OK);
EXPECT_EQ(kFrameDurationInMs * 5, GetCurrentTimestampInMs());
Shutdown();
}
TEST_F(VideoRendererBaseTest, Preroll_RightAfter) {
Initialize();
Pause();
Flush();
QueuePrerollFrames(kFrameDurationInMs * 6);
Preroll(kFrameDurationInMs * 6 + 1, PIPELINE_OK);
EXPECT_EQ(kFrameDurationInMs * 6, GetCurrentTimestampInMs());
Shutdown();
}
TEST_F(VideoRendererBaseTest, GetCurrentFrame_Initialized) {
Initialize();
EXPECT_TRUE(GetCurrentFrame()); // Due to prerolling.
Shutdown();
}
TEST_F(VideoRendererBaseTest, GetCurrentFrame_Playing) {
Initialize();
Play();
EXPECT_TRUE(GetCurrentFrame());
Shutdown();
}
TEST_F(VideoRendererBaseTest, GetCurrentFrame_Paused) {
Initialize();
Play();
Pause();
EXPECT_TRUE(GetCurrentFrame());
Shutdown();
}
TEST_F(VideoRendererBaseTest, GetCurrentFrame_Flushed) {
Initialize();
Play();
Pause();
Flush();
EXPECT_FALSE(GetCurrentFrame());
Shutdown();
}
#if defined(OS_MACOSX) || defined(ADDRESS_SANITIZER)
// 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();
// Preroll only end of stream frames.
QueueEndOfStream();
Preroll(0, PIPELINE_OK);
EXPECT_FALSE(GetCurrentFrame());
// Start playing, we should immediately get notified of end of stream.
Play();
WaitForEnded();
Shutdown();
}
TEST_F(VideoRendererBaseTest, GetCurrentFrame_Shutdown) {
Initialize();
Shutdown();
EXPECT_FALSE(GetCurrentFrame());
}
// Stop() is called immediately during an error.
TEST_F(VideoRendererBaseTest, GetCurrentFrame_Error) {
Initialize();
Stop();
EXPECT_FALSE(GetCurrentFrame());
}
// 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.
WaitableMessageLoopEvent event;
renderer_->Flush(event.GetClosure());
// Return the frame and wait.
renderer_->PutCurrentFrame(frame);
event.RunAndWait();
Stop();
}
// Verify that a late decoder response doesn't break invariants in the renderer.
TEST_F(VideoRendererBaseTest, StopDuringOutstandingRead) {
Initialize();
Play();
// Advance time a bit to trigger a Read().
AdvanceTimeInMs(kFrameDurationInMs);
WaitForPendingRead();
WaitableMessageLoopEvent event;
renderer_->Stop(event.GetClosure());
QueueEndOfStream();
SatisfyPendingRead();
event.RunAndWait();
}
TEST_F(VideoRendererBaseTest, AbortPendingRead_Playing) {
Initialize();
Play();
// Advance time a bit to trigger a Read().
AdvanceTimeInMs(kFrameDurationInMs);
WaitForPendingRead();
QueueAbortedRead();
SatisfyPendingRead();
Pause();
Flush();
QueuePrerollFrames(kFrameDurationInMs * 6);
Preroll(kFrameDurationInMs * 6, PIPELINE_OK);
EXPECT_EQ(kFrameDurationInMs * 6, GetCurrentTimestampInMs());
Shutdown();
}
TEST_F(VideoRendererBaseTest, AbortPendingRead_Flush) {
Initialize();
Play();
// Advance time a bit to trigger a Read().
AdvanceTimeInMs(kFrameDurationInMs);
WaitForPendingRead();
Pause();
Flush();
Shutdown();
}
TEST_F(VideoRendererBaseTest, AbortPendingRead_Preroll) {
Initialize();
Pause();
Flush();
QueueAbortedRead();
Preroll(kFrameDurationInMs * 6, PIPELINE_OK);
Shutdown();
}
TEST_F(VideoRendererBaseTest, VideoDecoder_InitFailure) {
InSequence s;
EXPECT_CALL(*decoder_, Initialize(_, _, _))
.WillOnce(RunCallback<1>(DECODER_ERROR_NOT_SUPPORTED));
InitializeRenderer(DECODER_ERROR_NOT_SUPPORTED);
Stop();
}
} // namespace media