| 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
|
|
|