Index: media/filters/ffmpeg_video_decoder_unittest.cc |
diff --git a/media/filters/ffmpeg_video_decoder_unittest.cc b/media/filters/ffmpeg_video_decoder_unittest.cc |
index a2ba238f1fa18b4f74485c7c8c1313199b1e69b8..bae062f8e60ed292cffa7def7f95af85517e1ac8 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 |