diff options
author | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-28 03:27:25 +0000 |
---|---|---|
committer | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-28 03:27:25 +0000 |
commit | 0cd3abfaa5e7248db27b0fd3e0c0ee8e47299eac (patch) | |
tree | 82119d1efe7d26f8af1ff29fcc026824ba9a178e /media | |
parent | cc97e48a8bfc2fdb5c3c2c8dc7204b33ff86b56d (diff) | |
download | chromium_src-0cd3abfaa5e7248db27b0fd3e0c0ee8e47299eac.zip chromium_src-0cd3abfaa5e7248db27b0fd3e0c0ee8e47299eac.tar.gz chromium_src-0cd3abfaa5e7248db27b0fd3e0c0ee8e47299eac.tar.bz2 |
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
Diffstat (limited to 'media')
-rw-r--r-- | media/filters/ffmpeg_video_decoder_unittest.cc | 733 |
1 files changed, 434 insertions, 299 deletions
diff --git a/media/filters/ffmpeg_video_decoder_unittest.cc b/media/filters/ffmpeg_video_decoder_unittest.cc index a2ba238..bae062f 100644 --- a/media/filters/ffmpeg_video_decoder_unittest.cc +++ b/media/filters/ffmpeg_video_decoder_unittest.cc @@ -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 |