Commit 0cd3abfa authored by scherkus@chromium.org's avatar scherkus@chromium.org

Rewrite FFmpegVideoDecoder tests based on existing FFmpegVideoDecodeEngine tests.

The old tests were mostly useless as they tested using a mocked VideoDecodeEngine.

Review URL: http://codereview.chromium.org/8340008

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@107681 0039d316-1c4b-4281-b951-d872f2087c98
parent cc97e48a
......@@ -10,389 +10,524 @@
#include "base/string_util.h"
#include "media/base/data_buffer.h"
#include "media/base/filters.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/test_data_util.h"
#include "media/base/video_frame.h"
#include "media/ffmpeg/ffmpeg_common.h"
#include "media/filters/ffmpeg_glue.h"
#include "media/filters/ffmpeg_video_decoder.h"
#include "media/video/video_decode_engine.h"
#include "media/video/video_decode_context.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/gmock/include/gmock/gmock.h"
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::DoAll;
using ::testing::Message;
using ::testing::Return;
using ::testing::ReturnNull;
using ::testing::Invoke;
using ::testing::ReturnRef;
using ::testing::SetArgumentPointee;
using ::testing::SaveArg;
using ::testing::StrictMock;
using ::testing::WithArg;
using ::testing::Invoke;
namespace media {
static const VideoFrame::Format kVideoFormat = VideoFrame::YV12;
static const gfx::Size kCodedSize(1280, 720);
static const gfx::Rect kVisibleRect(1280, 720);
static const gfx::Size kNaturalSize(1280, 720);
static const gfx::Size kCodedSize(320, 240);
static const gfx::Rect kVisibleRect(320, 240);
static const gfx::Size kNaturalSize(522, 288);
static const AVRational kFrameRate = { 100, 1 };
static const AVRational kAspectRatio = { 1, 1 };
// Holds timestamp and duration data needed for properly enqueuing a frame.
struct TimeTuple {
base::TimeDelta timestamp;
base::TimeDelta duration;
};
ACTION_P(ReturnBuffer, buffer) {
arg0.Run(buffer);
}
static const TimeTuple kTestPts1 =
{ base::TimeDelta::FromMicroseconds(123),
base::TimeDelta::FromMicroseconds(50) };
static const TimeTuple kTestPts2 =
{ base::TimeDelta::FromMicroseconds(456),
base::TimeDelta::FromMicroseconds(60) };
static const TimeTuple kTestPts3 =
{ base::TimeDelta::FromMicroseconds(789),
base::TimeDelta::FromMicroseconds(60) };
static const PipelineStatistics kStatistics;
// TODO(hclam): Share this in a separate file.
class MockVideoDecodeEngine : public VideoDecodeEngine {
class FFmpegVideoDecoderTest : public testing::Test {
public:
MOCK_METHOD4(Initialize, void(MessageLoop* message_loop,
VideoDecodeEngine::EventHandler* event_handler,
VideoDecodeContext* context,
const VideoDecoderConfig& config));
MOCK_METHOD1(ConsumeVideoSample, void(scoped_refptr<Buffer> buffer));
MOCK_METHOD1(ProduceVideoFrame, void(scoped_refptr<VideoFrame> buffer));
MOCK_METHOD0(Uninitialize, void());
MOCK_METHOD0(Flush, void());
MOCK_METHOD0(Seek, void());
MockVideoDecodeEngine() : event_handler_(NULL) {}
VideoDecodeEngine::EventHandler* event_handler_;
};
FFmpegVideoDecoderTest()
: decoder_(new FFmpegVideoDecoder(&message_loop_, NULL)),
demuxer_(new StrictMock<MockDemuxerStream>()) {
CHECK(FFmpegGlue::GetInstance());
// Class that just mocks the private functions.
class DecoderPrivateMock : public FFmpegVideoDecoder {
public:
DecoderPrivateMock(MessageLoop* message_loop,
VideoDecodeContext* context)
: FFmpegVideoDecoder(message_loop, context) {
decoder_->set_host(&host_);
decoder_->set_consume_video_frame_callback(base::Bind(
&FFmpegVideoDecoderTest::ConsumeVideoFrame, base::Unretained(this)));
// Initialize various test buffers.
frame_buffer_.reset(new uint8[kCodedSize.GetArea()]);
end_of_stream_buffer_ = new DataBuffer(0);
ReadTestDataFile("vp8-I-frame-320x240", &i_frame_buffer_);
ReadTestDataFile("vp8-corrupt-I-frame", &corrupt_i_frame_buffer_);
config_.Initialize(kCodecVP8, kVideoFormat, kCodedSize, kVisibleRect,
kFrameRate.num, kFrameRate.den,
kAspectRatio.num, kAspectRatio.den,
NULL, 0);
}
// change access qualifier for test: used in actions.
void ProduceVideoSample(scoped_refptr<Buffer> buffer) {
FFmpegVideoDecoder::ProduceVideoSample(buffer);
virtual ~FFmpegVideoDecoderTest() {}
void Initialize() {
InitializeWithConfig(config_);
}
void ConsumeVideoFrame(scoped_refptr<VideoFrame> frame,
const PipelineStatistics& statistics) {
FFmpegVideoDecoder::ConsumeVideoFrame(frame, statistics);
void InitializeWithConfig(const VideoDecoderConfig& config) {
EXPECT_CALL(*demuxer_, video_decoder_config())
.WillOnce(ReturnRef(config));
decoder_->Initialize(demuxer_, NewExpectedClosure(),
base::Bind(&MockStatisticsCallback::OnStatistics,
base::Unretained(&statistics_callback_)));
message_loop_.RunAllPending();
}
void OnReadComplete(Buffer* buffer) {
FFmpegVideoDecoder::OnReadComplete(buffer);
void Pause() {
decoder_->Pause(NewExpectedClosure());
message_loop_.RunAllPending();
}
};
ACTION_P2(EngineInitialize, engine, success) {
engine->event_handler_ = arg1;
engine->event_handler_->OnInitializeComplete(success);
}
void Flush() {
decoder_->Flush(NewExpectedClosure());
message_loop_.RunAllPending();
}
ACTION_P(EngineUninitialize, engine) {
if (engine->event_handler_)
engine->event_handler_->OnUninitializeComplete();
}
void Seek(int64 timestamp) {
decoder_->Seek(base::TimeDelta::FromMicroseconds(timestamp),
NewExpectedStatusCB(PIPELINE_OK));
message_loop_.RunAllPending();
}
ACTION_P(EngineFlush, engine) {
if (engine->event_handler_)
engine->event_handler_->OnFlushComplete();
}
void Stop() {
decoder_->Stop(NewExpectedClosure());
message_loop_.RunAllPending();
}
ACTION_P(EngineSeek, engine) {
if (engine->event_handler_)
engine->event_handler_->OnSeekComplete();
}
// Sets up expectations for FFmpegVideoDecodeEngine to preroll after
// receiving a Seek(). The adjustment on Read() is due to the decoder
// delaying frame output.
//
// TODO(scherkus): this is madness -- there's no reason for a decoder to
// assume it should preroll anything.
void ExpectSeekPreroll() {
EXPECT_CALL(*demuxer_, Read(_))
.Times(Limits::kMaxVideoFrames + 1)
.WillRepeatedly(ReturnBuffer(i_frame_buffer_));
EXPECT_CALL(statistics_callback_, OnStatistics(_))
.Times(Limits::kMaxVideoFrames);
EXPECT_CALL(*this, ConsumeVideoFrame(_))
.Times(Limits::kMaxVideoFrames);
}
// Fixture class to facilitate writing tests. Takes care of setting up the
// FFmpeg, pipeline and filter host mocks.
class FFmpegVideoDecoderTest : public testing::Test {
protected:
FFmpegVideoDecoderTest() {
// Create an FFmpegVideoDecoder, and MockVideoDecodeEngine.
//
// TODO(ajwong): Break the test's dependency on FFmpegVideoDecoder.
decoder_ = new DecoderPrivateMock(&message_loop_, NULL);
renderer_ = new MockVideoRenderer();
engine_ = new StrictMock<MockVideoDecodeEngine>();
// Inject mocks and prepare a demuxer stream.
decoder_->set_host(&host_);
decoder_->set_consume_video_frame_callback(
base::Bind(&MockVideoRenderer::ConsumeVideoFrame,
base::Unretained(renderer_.get())));
decoder_->SetVideoDecodeEngineForTest(engine_);
demuxer_ = new StrictMock<MockDemuxerStream>();
// Initialize FFmpeg fixtures.
memset(&yuv_frame_, 0, sizeof(yuv_frame_));
base::TimeDelta zero;
video_frame_ = VideoFrame::CreateFrame(VideoFrame::YV12,
kVisibleRect.width(),
kVisibleRect.height(),
zero, zero);
buffer_ = new DataBuffer(1);
end_of_stream_buffer_ = new DataBuffer(0);
// Sets up expectations for FFmpegVideoDecodeEngine to preroll after
// receiving a Seek() but for the end of stream case.
//
// TODO(scherkus): this is madness -- there's no reason for a decoder to
// assume it should preroll anything.
void ExpectSeekPrerollEndOfStream() {
EXPECT_CALL(*demuxer_, Read(_))
.Times(Limits::kMaxVideoFrames)
.WillRepeatedly(ReturnBuffer(end_of_stream_buffer_));
EXPECT_CALL(statistics_callback_, OnStatistics(_))
.Times(Limits::kMaxVideoFrames);
}
EXPECT_CALL(stats_callback_object_, OnStatistics(_))
.Times(AnyNumber());
// Sets up expectations and actions to put FFmpegVideoDecoder in an active
// decoding state.
void EnterDecodingState() {
scoped_refptr<VideoFrame> video_frame;
DecodeSingleFrame(i_frame_buffer_, &video_frame);
config_.Initialize(kCodecVP8, kVideoFormat, kCodedSize, kVisibleRect,
kFrameRate.num, kFrameRate.den,
kAspectRatio.num, kAspectRatio.den,
NULL, 0);
ASSERT_TRUE(video_frame);
EXPECT_FALSE(video_frame->IsEndOfStream());
}
virtual ~FFmpegVideoDecoderTest() {
// The presence of an event handler means we need to uninitialize.
if (engine_->event_handler_) {
EXPECT_CALL(*engine_, Uninitialize())
.WillOnce(EngineUninitialize(engine_));
}
// Sets up expectations and actions to put FFmpegVideoDecoder in an end
// of stream state.
void EnterEndOfStreamState() {
EXPECT_CALL(statistics_callback_, OnStatistics(_));
decoder_->Stop(NewExpectedClosure());
scoped_refptr<VideoFrame> video_frame;
CallProduceVideoFrame(&video_frame);
ASSERT_TRUE(video_frame);
EXPECT_TRUE(video_frame->IsEndOfStream());
}
// Finish up any remaining tasks.
message_loop_.RunAllPending();
// Decodes the single compressed frame in |buffer| and writes the
// uncompressed output to |video_frame|. This method works with single
// and multithreaded decoders. End of stream buffers are used to trigger
// the frame to be returned in the multithreaded decoder case.
void DecodeSingleFrame(const scoped_refptr<Buffer>& buffer,
scoped_refptr<VideoFrame>* video_frame) {
EXPECT_CALL(*demuxer_, Read(_))
.WillOnce(ReturnBuffer(buffer))
.WillRepeatedly(ReturnBuffer(end_of_stream_buffer_));
EXPECT_CALL(statistics_callback_, OnStatistics(_));
CallProduceVideoFrame(video_frame);
}
void InitializeDecoderSuccessfully() {
EXPECT_CALL(*demuxer_, video_decoder_config())
.WillOnce(ReturnRef(config_));
// Decodes |i_frame_buffer_| and then decodes the data contained in
// the file named |test_file_name|. This function expects both buffers
// to decode to frames that are the same size.
void DecodeIFrameThenTestFile(const std::string& test_file_name) {
Initialize();
scoped_refptr<VideoFrame> video_frame_a;
scoped_refptr<VideoFrame> video_frame_b;
scoped_refptr<Buffer> buffer;
ReadTestDataFile(test_file_name, &buffer);
EXPECT_CALL(*demuxer_, Read(_))
.WillOnce(ReturnBuffer(i_frame_buffer_))
.WillOnce(ReturnBuffer(buffer))
.WillRepeatedly(ReturnBuffer(end_of_stream_buffer_));
EXPECT_CALL(statistics_callback_, OnStatistics(_))
.Times(2);
CallProduceVideoFrame(&video_frame_a);
CallProduceVideoFrame(&video_frame_b);
size_t expected_width = static_cast<size_t>(kVisibleRect.width());
size_t expected_height = static_cast<size_t>(kVisibleRect.height());
ASSERT_TRUE(video_frame_a);
ASSERT_TRUE(video_frame_b);
EXPECT_EQ(expected_width, video_frame_a->width());
EXPECT_EQ(expected_height, video_frame_a->height());
EXPECT_EQ(expected_width, video_frame_b->width());
EXPECT_EQ(expected_height, video_frame_b->height());
}
EXPECT_CALL(*engine_, Initialize(_, _, _, _))
.WillOnce(EngineInitialize(engine_, true));
void CallProduceVideoFrame(scoped_refptr<VideoFrame>* video_frame) {
EXPECT_CALL(*this, ConsumeVideoFrame(_))
.WillOnce(SaveArg<0>(video_frame));
decoder_->ProduceVideoFrame(VideoFrame::CreateFrame(
VideoFrame::YV12, kVisibleRect.width(), kVisibleRect.height(),
kNoTimestamp, kNoTimestamp));
decoder_->Initialize(demuxer_,
NewExpectedClosure(), NewStatisticsCallback());
message_loop_.RunAllPending();
}
StatisticsCallback NewStatisticsCallback() {
return base::Bind(&MockStatisticsCallback::OnStatistics,
base::Unretained(&stats_callback_object_));
void SetupTimestampTest() {
Initialize();
EXPECT_CALL(*demuxer_, Read(_))
.WillRepeatedly(Invoke(this, &FFmpegVideoDecoderTest::ReadTimestamp));
EXPECT_CALL(statistics_callback_, OnStatistics(_))
.Times(AnyNumber());
}
void PushTimestamp(int64 timestamp) {
timestamps_.push_back(timestamp);
}
int64 PopTimestamp() {
scoped_refptr<VideoFrame> video_frame;
CallProduceVideoFrame(&video_frame);
return video_frame->GetTimestamp().InMicroseconds();
}
void ReadTimestamp(const DemuxerStream::ReadCallback& read_callback) {
if (timestamps_.empty()) {
read_callback.Run(end_of_stream_buffer_);
return;
}
i_frame_buffer_->SetTimestamp(
base::TimeDelta::FromMicroseconds(timestamps_.front()));
timestamps_.pop_front();
read_callback.Run(i_frame_buffer_);
}
// Fixture members.
MockVideoDecodeEngine* engine_; // Owned by |decoder_|.
scoped_refptr<DecoderPrivateMock> decoder_;
scoped_refptr<MockVideoRenderer> renderer_;
MOCK_METHOD1(ConsumeVideoFrame, void(scoped_refptr<VideoFrame>));
MessageLoop message_loop_;
scoped_refptr<FFmpegVideoDecoder> decoder_;
scoped_refptr<StrictMock<MockDemuxerStream> > demuxer_;
scoped_refptr<DataBuffer> buffer_;
scoped_refptr<DataBuffer> end_of_stream_buffer_;
MockStatisticsCallback stats_callback_object_;
MockStatisticsCallback statistics_callback_;
StrictMock<MockFilterHost> host_;
MessageLoop message_loop_;
VideoDecoderConfig config_;
// FFmpeg fixtures.
AVFrame yuv_frame_;
scoped_refptr<VideoFrame> video_frame_;
// Various buffers for testing.
scoped_array<uint8_t> frame_buffer_;
scoped_refptr<Buffer> end_of_stream_buffer_;
scoped_refptr<Buffer> i_frame_buffer_;
scoped_refptr<Buffer> corrupt_i_frame_buffer_;
VideoDecoderConfig config_;
// Used for generating timestamped buffers.
std::deque<int64> timestamps_;
private:
DISALLOW_COPY_AND_ASSIGN(FFmpegVideoDecoderTest);
};
TEST_F(FFmpegVideoDecoderTest, Initialize_EngineFails) {
EXPECT_CALL(*demuxer_, video_decoder_config())
.WillOnce(ReturnRef(config_));
TEST_F(FFmpegVideoDecoderTest, Initialize_Normal) {
Initialize();
}
EXPECT_CALL(*engine_, Initialize(_, _, _, _))
.WillOnce(EngineInitialize(engine_, false));
TEST_F(FFmpegVideoDecoderTest, Initialize_FindDecoderFails) {
// Test avcodec_find_decoder() returning NULL.
VideoDecoderConfig config(kUnknownVideoCodec, kVideoFormat,
kCodedSize, kVisibleRect,
kFrameRate.num, kFrameRate.den,
kAspectRatio.num, kAspectRatio.den,
NULL, 0);
EXPECT_CALL(host_, SetError(PIPELINE_ERROR_DECODE));
InitializeWithConfig(config);
}
decoder_->Initialize(demuxer_,
NewExpectedClosure(), NewStatisticsCallback());
message_loop_.RunAllPending();
TEST_F(FFmpegVideoDecoderTest, Initialize_OpenDecoderFails) {
// Specify Theora w/o extra data so that avcodec_open() fails.
VideoDecoderConfig config(kCodecTheora, kVideoFormat,
kCodedSize, kVisibleRect,
kFrameRate.num, kFrameRate.den,
kAspectRatio.num, kAspectRatio.den,
NULL, 0);
EXPECT_CALL(host_, SetError(PIPELINE_ERROR_DECODE));
InitializeWithConfig(config);
}
TEST_F(FFmpegVideoDecoderTest, Initialize_Successful) {
InitializeDecoderSuccessfully();
TEST_F(FFmpegVideoDecoderTest, DecodeFrame_Normal) {
Initialize();
// Test that the uncompressed video surface matches the dimensions
// specified by FFmpeg.
EXPECT_EQ(kNaturalSize, decoder_->natural_size());
// Simulate decoding a single frame.
scoped_refptr<VideoFrame> video_frame;
DecodeSingleFrame(i_frame_buffer_, &video_frame);
ASSERT_TRUE(video_frame);
EXPECT_FALSE(video_frame->IsEndOfStream());
}
TEST_F(FFmpegVideoDecoderTest, OnError) {
InitializeDecoderSuccessfully();
// Verify current behavior for 0 byte frames. FFmpeg simply ignores
// the 0 byte frames.
TEST_F(FFmpegVideoDecoderTest, DecodeFrame_0ByteFrame) {
Initialize();
scoped_refptr<DataBuffer> zero_byte_buffer = new DataBuffer(1);
scoped_refptr<VideoFrame> video_frame_a;
scoped_refptr<VideoFrame> video_frame_b;
scoped_refptr<VideoFrame> video_frame_c;
EXPECT_CALL(*demuxer_, Read(_))
.WillOnce(ReturnBuffer(i_frame_buffer_))
.WillOnce(ReturnBuffer(zero_byte_buffer))
.WillOnce(ReturnBuffer(i_frame_buffer_))
.WillRepeatedly(ReturnBuffer(end_of_stream_buffer_));
scoped_refptr<VideoFrame> null_frame;
EXPECT_CALL(*renderer_, ConsumeVideoFrame(null_frame));
engine_->event_handler_->OnError();
EXPECT_CALL(statistics_callback_, OnStatistics(_))
.Times(3);
CallProduceVideoFrame(&video_frame_a);
CallProduceVideoFrame(&video_frame_b);
CallProduceVideoFrame(&video_frame_c);
ASSERT_TRUE(video_frame_a);
ASSERT_TRUE(video_frame_b);
ASSERT_TRUE(video_frame_a);
EXPECT_FALSE(video_frame_a->IsEndOfStream());
EXPECT_FALSE(video_frame_b->IsEndOfStream());
EXPECT_TRUE(video_frame_c->IsEndOfStream());
}
TEST_F(FFmpegVideoDecoderTest, DecodeFrame_DecodeError) {
Initialize();
EXPECT_CALL(*demuxer_, Read(_))
.WillOnce(ReturnBuffer(corrupt_i_frame_buffer_))
.WillRepeatedly(ReturnBuffer(i_frame_buffer_));
ACTION_P2(ReadFromDemux, decoder, buffer) {
decoder->ProduceVideoSample(buffer);
scoped_refptr<VideoFrame> video_frame;
CallProduceVideoFrame(&video_frame);
// XXX: SERIOUSLY? This seems broken to call NULL on decoder error.
EXPECT_FALSE(video_frame);
}
ACTION_P3(ReturnFromDemux, decoder, buffer, time_tuple) {
buffer->SetTimestamp(time_tuple.timestamp);
buffer->SetDuration(time_tuple.duration);
decoder->OnReadComplete(buffer);
// Multi-threaded decoders have different behavior than single-threaded
// decoders at the end of the stream. Multithreaded decoders hide errors
// that happen on the last |codec_context_->thread_count| frames to avoid
// prematurely signalling EOS. This test just exposes that behavior so we can
// detect if it changes.
TEST_F(FFmpegVideoDecoderTest, DecodeFrame_DecodeErrorAtEndOfStream) {
Initialize();
scoped_refptr<VideoFrame> video_frame;
DecodeSingleFrame(corrupt_i_frame_buffer_, &video_frame);
ASSERT_TRUE(video_frame);
EXPECT_TRUE(video_frame->IsEndOfStream());
}
ACTION_P4(DecodeComplete, decoder, video_frame, time_tuple, statistics) {
video_frame->SetTimestamp(time_tuple.timestamp);
video_frame->SetDuration(time_tuple.duration);
decoder->ConsumeVideoFrame(video_frame, statistics);
// Decode |i_frame_buffer_| and then a frame with a larger width and verify
// the output size didn't change.
// TODO(acolwell): Fix InvalidRead detected by Valgrind
//TEST_F(FFmpegVideoDecoderTest, DecodeFrame_LargerWidth) {
// DecodeIFrameThenTestFile("vp8-I-frame-640x240");
//}
// Decode |i_frame_buffer_| and then a frame with a smaller width and verify
// the output size didn't change.
TEST_F(FFmpegVideoDecoderTest, DecodeFrame_SmallerWidth) {
DecodeIFrameThenTestFile("vp8-I-frame-160x240");
}
ACTION_P3(DecodeNotComplete, decoder, buffer, statistics) {
scoped_refptr<VideoFrame> null_frame;
if (buffer->IsEndOfStream()) // We had started flushing.
decoder->ConsumeVideoFrame(null_frame, statistics);
else
decoder->ProduceVideoSample(buffer);
// Decode |i_frame_buffer_| and then a frame with a larger height and verify
// the output size didn't change.
// TODO(acolwell): Fix InvalidRead detected by Valgrind
//TEST_F(FFmpegVideoDecoderTest, DecodeFrame_LargerHeight) {
// DecodeIFrameThenTestFile("vp8-I-frame-320x480");
//}
// Decode |i_frame_buffer_| and then a frame with a smaller height and verify
// the output size didn't change.
TEST_F(FFmpegVideoDecoderTest, DecodeFrame_SmallerHeight) {
DecodeIFrameThenTestFile("vp8-I-frame-320x120");
}
ACTION_P(ConsumePTS, pts_heap) {
pts_heap->Pop();
// Test pausing when decoder has initialized but not decoded.
TEST_F(FFmpegVideoDecoderTest, Pause_Initialized) {
Initialize();
Pause();
}
TEST_F(FFmpegVideoDecoderTest, DoDecode_TestStateTransition) {
// Simulates a input sequence of three buffers, and six decode requests to
// exercise the state transitions, and bookkeeping logic of DoDecode.
//
// We try to verify the following:
// 1) Non-EoS buffer timestamps are pushed into the pts_heap.
// 2) Timestamps are popped for each decoded frame.
// 3) The last_pts_ is updated for each decoded frame.
// 4) kDecodeFinished is never left regardless of what kind of buffer is
// given.
// 5) All state transitions happen as expected.
InitializeDecoderSuccessfully();
// Setup initial state and check that it is sane.
ASSERT_EQ(FFmpegVideoDecoder::kNormal, decoder_->state_);
ASSERT_TRUE(base::TimeDelta() == decoder_->pts_stream_.current_pts());
ASSERT_TRUE(base::TimeDelta() == decoder_->pts_stream_.current_duration());
// Setup decoder to buffer one frame, decode one frame, fail one frame,
// decode one more, and then fail the last one to end decoding.
EXPECT_CALL(*engine_, ProduceVideoFrame(_))
.Times(4)
.WillRepeatedly(ReadFromDemux(decoder_.get(), buffer_));
EXPECT_CALL(*demuxer_.get(), Read(_))
.Times(6)
.WillOnce(ReturnFromDemux(decoder_.get(), buffer_, kTestPts1))
.WillOnce(ReturnFromDemux(decoder_.get(), buffer_, kTestPts3))
.WillOnce(ReturnFromDemux(decoder_.get(), buffer_, kTestPts2))
.WillOnce(ReturnFromDemux(decoder_.get(),
end_of_stream_buffer_, kTestPts3))
.WillOnce(ReturnFromDemux(decoder_.get(),
end_of_stream_buffer_, kTestPts3))
.WillOnce(ReturnFromDemux(decoder_.get(),
end_of_stream_buffer_, kTestPts3));
EXPECT_CALL(*engine_, ConsumeVideoSample(_))
.WillOnce(DecodeNotComplete(decoder_.get(), buffer_, kStatistics))
.WillOnce(DecodeComplete(decoder_.get(),
video_frame_, kTestPts1, kStatistics))
.WillOnce(DecodeNotComplete(decoder_.get(),
buffer_, kStatistics))
.WillOnce(DecodeComplete(decoder_.get(),
video_frame_, kTestPts2, kStatistics))
.WillOnce(DecodeComplete(decoder_.get(),
video_frame_, kTestPts3, kStatistics))
.WillOnce(DecodeNotComplete(decoder_.get(),
end_of_stream_buffer_, kStatistics));
EXPECT_CALL(*renderer_.get(), ConsumeVideoFrame(_))
.Times(4);
EXPECT_CALL(stats_callback_object_, OnStatistics(_))
.Times(4);
// First request from renderer: at first round decode engine did not produce
// any frame. Decoder will issue another read from demuxer. at second round
// decode engine will get a valid frame.
decoder_->ProduceVideoFrame(video_frame_);
message_loop_.RunAllPending();
EXPECT_EQ(FFmpegVideoDecoder::kNormal, decoder_->state_);
ASSERT_TRUE(kTestPts1.timestamp == decoder_->pts_stream_.current_pts());
ASSERT_TRUE(kTestPts1.duration == decoder_->pts_stream_.current_duration());
// Second request from renderer: at first round decode engine did not produce
// any frame. Decoder will issue another read from demuxer. at second round
// decode engine will get a valid frame.
decoder_->ProduceVideoFrame(video_frame_);
message_loop_.RunAllPending();
EXPECT_EQ(FFmpegVideoDecoder::kFlushCodec, decoder_->state_);
EXPECT_TRUE(kTestPts2.timestamp == decoder_->pts_stream_.current_pts());
EXPECT_TRUE(kTestPts2.duration == decoder_->pts_stream_.current_duration());
// Third request from renderer: decode engine will return frame on the
// first round. Input stream had reach EOS, therefore we had entered
// kFlushCodec state after this call.
decoder_->ProduceVideoFrame(video_frame_);
message_loop_.RunAllPending();
EXPECT_EQ(FFmpegVideoDecoder::kFlushCodec, decoder_->state_);
EXPECT_TRUE(kTestPts3.timestamp == decoder_->pts_stream_.current_pts());
EXPECT_TRUE(kTestPts3.duration == decoder_->pts_stream_.current_duration());
// Fourth request from renderer: Both input/output reach EOF. therefore
// we had reached the kDecodeFinished state after this call.
decoder_->ProduceVideoFrame(video_frame_);
message_loop_.RunAllPending();
EXPECT_EQ(FFmpegVideoDecoder::kDecodeFinished, decoder_->state_);
EXPECT_TRUE(kTestPts3.timestamp == decoder_->pts_stream_.current_pts());
EXPECT_TRUE(kTestPts3.duration == decoder_->pts_stream_.current_duration());
// Test pausing when decoder has decoded single frame.
TEST_F(FFmpegVideoDecoderTest, Pause_Decoding) {
Initialize();
EnterDecodingState();
Pause();
}
TEST_F(FFmpegVideoDecoderTest, DoSeek) {
// Simulates receiving a call to DoSeek() while in every possible state. In
// every case, it should clear the timestamp queue, flush the decoder and
// reset the state to kNormal.
const base::TimeDelta kZero;
const FFmpegVideoDecoder::DecoderState kStates[] = {
FFmpegVideoDecoder::kNormal,
FFmpegVideoDecoder::kFlushCodec,
FFmpegVideoDecoder::kDecodeFinished,
FFmpegVideoDecoder::kStopped,
};
InitializeDecoderSuccessfully();
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kStates); ++i) {
SCOPED_TRACE(Message() << "Iteration " << i);
// Push in some timestamps.
buffer_->SetTimestamp(kTestPts1.timestamp);
decoder_->pts_stream_.EnqueuePts(buffer_);
buffer_->SetTimestamp(kTestPts2.timestamp);
decoder_->pts_stream_.EnqueuePts(buffer_);
buffer_->SetTimestamp(kTestPts3.timestamp);
decoder_->pts_stream_.EnqueuePts(buffer_);
decoder_->state_ = kStates[i];
// Expect a flush.
EXPECT_CALL(*engine_, Flush())
.WillOnce(EngineFlush(engine_));
decoder_->Flush(NewExpectedClosure());
// Test pausing when decoder has hit end of stream.
TEST_F(FFmpegVideoDecoderTest, Pause_EndOfStream) {
Initialize();
EnterDecodingState();
EnterEndOfStreamState();
Pause();
}
// Expect Seek and verify the results.
EXPECT_CALL(*engine_, Seek())
.WillOnce(EngineSeek(engine_));
decoder_->Seek(kZero, NewExpectedStatusCB(PIPELINE_OK));
// Test flushing when decoder has initialized but not decoded.
TEST_F(FFmpegVideoDecoderTest, Flush_Initialized) {
Initialize();
Flush();
}
EXPECT_TRUE(kZero == decoder_->pts_stream_.current_duration());
EXPECT_EQ(FFmpegVideoDecoder::kNormal, decoder_->state_);
}
// Test flushing when decoder has decoded single frame.
TEST_F(FFmpegVideoDecoderTest, Flush_Decoding) {
Initialize();
EnterDecodingState();
Flush();
}
// Test flushing when decoder has hit end of stream.
//
// TODO(scherkus): test is disabled until we clean up buffer recycling.
TEST_F(FFmpegVideoDecoderTest, DISABLED_Flush_EndOfStream) {
Initialize();
EnterDecodingState();
EnterEndOfStreamState();
Flush();
}
// Test seeking when decoder has initialized but not decoded.
TEST_F(FFmpegVideoDecoderTest, Seek_Initialized) {
Initialize();
ExpectSeekPreroll();
Seek(1000);
}
// Test seeking when decoder has decoded single frame.
TEST_F(FFmpegVideoDecoderTest, Seek_Decoding) {
Initialize();
EnterDecodingState();
ExpectSeekPreroll();
Seek(1000);
}
// Test seeking when decoder has hit end of stream.
TEST_F(FFmpegVideoDecoderTest, Seek_EndOfStream) {
Initialize();
EnterDecodingState();
EnterEndOfStreamState();
ExpectSeekPrerollEndOfStream();
Seek(1000);
}
// Test stopping when decoder has initialized but not decoded.
TEST_F(FFmpegVideoDecoderTest, Stop_Initialized) {
Initialize();
Stop();
}
// Test stopping when decoder has decoded single frame.
TEST_F(FFmpegVideoDecoderTest, Stop_Decoding) {
Initialize();
EnterDecodingState();
Stop();
}
// Test stopping when decoder has hit end of stream.
TEST_F(FFmpegVideoDecoderTest, Stop_EndOfStream) {
Initialize();
EnterDecodingState();
EnterEndOfStreamState();
Stop();
}
// Test normal operation of timestamping where all input has valid timestamps.
TEST_F(FFmpegVideoDecoderTest, Timestamps_Normal) {
SetupTimestampTest();
PushTimestamp(0);
PushTimestamp(1000);
PushTimestamp(2000);
PushTimestamp(3000);
EXPECT_EQ(0, PopTimestamp());
EXPECT_EQ(1000, PopTimestamp());
EXPECT_EQ(2000, PopTimestamp());
EXPECT_EQ(3000, PopTimestamp());
}
// Test situation where some input timestamps are missing and estimation will
// be used based on the frame rate.
TEST_F(FFmpegVideoDecoderTest, Timestamps_Estimated) {
SetupTimestampTest();
PushTimestamp(0);
PushTimestamp(1000);
PushTimestamp(kNoTimestamp.InMicroseconds());
PushTimestamp(kNoTimestamp.InMicroseconds());
EXPECT_EQ(0, PopTimestamp());
EXPECT_EQ(1000, PopTimestamp());
EXPECT_EQ(11000, PopTimestamp());
EXPECT_EQ(21000, PopTimestamp());
}
// Test resulting timestamps from end of stream.
TEST_F(FFmpegVideoDecoderTest, Timestamps_EndOfStream) {
SetupTimestampTest();
PushTimestamp(0);
PushTimestamp(1000);
EXPECT_EQ(0, PopTimestamp());
EXPECT_EQ(1000, PopTimestamp());
// Following are all end of stream buffers.
EXPECT_EQ(0, PopTimestamp());
EXPECT_EQ(0, PopTimestamp());
EXPECT_EQ(0, PopTimestamp());
}
} // namespace media
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment