blob: e01d8b800de319da676b865cadff7ed15a0b80e4 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <list>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/memory/singleton.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/string_util.h"
#include "media/base/decoder_buffer.h"
#include "media/base/gmock_callback_support.h"
#include "media/base/limits.h"
#include "media/base/media_util.h"
#include "media/base/mock_filters.h"
#include "media/base/test_data_util.h"
#include "media/base/test_helpers.h"
#include "media/base/video_decoder.h"
#include "media/base/video_frame.h"
#include "media/base/video_util.h"
#include "media/ffmpeg/ffmpeg_common.h"
#include "media/filters/ffmpeg_glue.h"
#include "media/filters/ffmpeg_video_decoder.h"
#include "testing/gmock/include/gmock/gmock.h"
using ::testing::_;
using ::testing::AtLeast;
using ::testing::AtMost;
using ::testing::InSequence;
using ::testing::IsNull;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::StrictMock;
namespace media {
static const VideoPixelFormat kVideoFormat = PIXEL_FORMAT_YV12;
static const gfx::Size kCodedSize(320, 240);
static const gfx::Rect kVisibleRect(320, 240);
static const gfx::Size kNaturalSize(320, 240);
ACTION_P(ReturnBuffer, buffer) {
arg0.Run(buffer.get() ? DemuxerStream::kOk : DemuxerStream::kAborted, buffer);
}
class FFmpegVideoDecoderTest : public testing::Test {
public:
FFmpegVideoDecoderTest()
: decoder_(new FFmpegVideoDecoder()),
decode_cb_(base::Bind(&FFmpegVideoDecoderTest::DecodeDone,
base::Unretained(this))) {
FFmpegGlue::InitializeFFmpeg();
// Initialize various test buffers.
frame_buffer_.reset(new uint8[kCodedSize.GetArea()]);
end_of_stream_buffer_ = DecoderBuffer::CreateEOSBuffer();
i_frame_buffer_ = ReadTestDataFile("vp8-I-frame-320x240");
corrupt_i_frame_buffer_ = ReadTestDataFile("vp8-corrupt-I-frame");
}
virtual ~FFmpegVideoDecoderTest() {
Destroy();
}
void Initialize() {
InitializeWithConfig(TestVideoConfig::Normal());
}
void InitializeWithConfigWithResult(const VideoDecoderConfig& config,
bool success) {
decoder_->Initialize(config, false, SetCdmReadyCB(),
NewExpectedBoolCB(success),
base::Bind(&FFmpegVideoDecoderTest::FrameReady,
base::Unretained(this)));
message_loop_.RunUntilIdle();
}
void InitializeWithConfig(const VideoDecoderConfig& config) {
InitializeWithConfigWithResult(config, true);
}
void Reinitialize() {
InitializeWithConfig(TestVideoConfig::Large());
}
void Reset() {
decoder_->Reset(NewExpectedClosure());
message_loop_.RunUntilIdle();
}
void Destroy() {
decoder_.reset();
message_loop_.RunUntilIdle();
}
// Sets up expectations and actions to put FFmpegVideoDecoder in an active
// decoding state.
void EnterDecodingState() {
EXPECT_EQ(VideoDecoder::kOk, DecodeSingleFrame(i_frame_buffer_));
ASSERT_EQ(1U, output_frames_.size());
}
// Sets up expectations and actions to put FFmpegVideoDecoder in an end
// of stream state.
void EnterEndOfStreamState() {
EXPECT_EQ(VideoDecoder::kOk, DecodeSingleFrame(end_of_stream_buffer_));
ASSERT_FALSE(output_frames_.empty());
}
typedef std::vector<scoped_refptr<DecoderBuffer> > InputBuffers;
typedef std::vector<scoped_refptr<VideoFrame> > OutputFrames;
// Decodes all buffers in |input_buffers| and push all successfully decoded
// output frames into |output_frames|.
// Returns the last decode status returned by the decoder.
VideoDecoder::Status DecodeMultipleFrames(const InputBuffers& input_buffers) {
for (InputBuffers::const_iterator iter = input_buffers.begin();
iter != input_buffers.end();
++iter) {
VideoDecoder::Status status = Decode(*iter);
switch (status) {
case VideoDecoder::kOk:
break;
case VideoDecoder::kAborted:
NOTREACHED();
case VideoDecoder::kDecodeError:
DCHECK(output_frames_.empty());
return status;
}
}
return VideoDecoder::kOk;
}
// 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.
VideoDecoder::Status DecodeSingleFrame(
const scoped_refptr<DecoderBuffer>& buffer) {
InputBuffers input_buffers;
input_buffers.push_back(buffer);
input_buffers.push_back(end_of_stream_buffer_);
return DecodeMultipleFrames(input_buffers);
}
// 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,
int expected_width,
int expected_height) {
Initialize();
scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile(test_file_name);
InputBuffers input_buffers;
input_buffers.push_back(i_frame_buffer_);
input_buffers.push_back(buffer);
input_buffers.push_back(end_of_stream_buffer_);
VideoDecoder::Status status =
DecodeMultipleFrames(input_buffers);
EXPECT_EQ(VideoDecoder::kOk, status);
ASSERT_EQ(2U, output_frames_.size());
gfx::Size original_size = kVisibleRect.size();
EXPECT_EQ(original_size.width(),
output_frames_[0]->visible_rect().size().width());
EXPECT_EQ(original_size.height(),
output_frames_[0]->visible_rect().size().height());
EXPECT_EQ(expected_width,
output_frames_[1]->visible_rect().size().width());
EXPECT_EQ(expected_height,
output_frames_[1]->visible_rect().size().height());
}
VideoDecoder::Status Decode(const scoped_refptr<DecoderBuffer>& buffer) {
VideoDecoder::Status status;
EXPECT_CALL(*this, DecodeDone(_)).WillOnce(SaveArg<0>(&status));
decoder_->Decode(buffer, decode_cb_);
message_loop_.RunUntilIdle();
return status;
}
void FrameReady(const scoped_refptr<VideoFrame>& frame) {
DCHECK(!frame->metadata()->IsTrue(VideoFrameMetadata::END_OF_STREAM));
output_frames_.push_back(frame);
}
MOCK_METHOD1(DecodeDone, void(VideoDecoder::Status));
base::MessageLoop message_loop_;
scoped_ptr<FFmpegVideoDecoder> decoder_;
VideoDecoder::DecodeCB decode_cb_;
// Various buffers for testing.
scoped_ptr<uint8_t[]> frame_buffer_;
scoped_refptr<DecoderBuffer> end_of_stream_buffer_;
scoped_refptr<DecoderBuffer> i_frame_buffer_;
scoped_refptr<DecoderBuffer> corrupt_i_frame_buffer_;
OutputFrames output_frames_;
private:
DISALLOW_COPY_AND_ASSIGN(FFmpegVideoDecoderTest);
};
TEST_F(FFmpegVideoDecoderTest, Initialize_Normal) {
Initialize();
}
TEST_F(FFmpegVideoDecoderTest, Initialize_OpenDecoderFails) {
// Specify Theora w/o extra data so that avcodec_open2() fails.
VideoDecoderConfig config(kCodecTheora, VIDEO_CODEC_PROFILE_UNKNOWN,
kVideoFormat, COLOR_SPACE_UNSPECIFIED, kCodedSize,
kVisibleRect, kNaturalSize, EmptyExtraData(),
false);
InitializeWithConfigWithResult(config, false);
}
TEST_F(FFmpegVideoDecoderTest, Reinitialize_Normal) {
Initialize();
Reinitialize();
}
TEST_F(FFmpegVideoDecoderTest, Reinitialize_AfterDecodeFrame) {
Initialize();
EnterDecodingState();
Reinitialize();
}
TEST_F(FFmpegVideoDecoderTest, Reinitialize_AfterReset) {
Initialize();
EnterDecodingState();
Reset();
Reinitialize();
}
TEST_F(FFmpegVideoDecoderTest, DecodeFrame_Normal) {
Initialize();
// Simulate decoding a single frame.
EXPECT_EQ(VideoDecoder::kOk, DecodeSingleFrame(i_frame_buffer_));
ASSERT_EQ(1U, output_frames_.size());
}
// Verify current behavior for 0 byte frames. FFmpeg simply ignores
// the 0 byte frames.
TEST_F(FFmpegVideoDecoderTest, DecodeFrame_0ByteFrame) {
Initialize();
scoped_refptr<DecoderBuffer> zero_byte_buffer = new DecoderBuffer(0);
InputBuffers input_buffers;
input_buffers.push_back(i_frame_buffer_);
input_buffers.push_back(zero_byte_buffer);
input_buffers.push_back(i_frame_buffer_);
input_buffers.push_back(end_of_stream_buffer_);
VideoDecoder::Status status = DecodeMultipleFrames(input_buffers);
EXPECT_EQ(VideoDecoder::kOk, status);
ASSERT_EQ(2U, output_frames_.size());
}
TEST_F(FFmpegVideoDecoderTest, DecodeFrame_DecodeError) {
Initialize();
// The error is only raised on the second decode attempt, so we expect at
// least one successful decode but we don't expect valid frame to be decoded.
// During the second decode attempt an error is raised.
EXPECT_EQ(VideoDecoder::kOk, Decode(corrupt_i_frame_buffer_));
EXPECT_TRUE(output_frames_.empty());
EXPECT_EQ(VideoDecoder::kDecodeError, Decode(i_frame_buffer_));
EXPECT_TRUE(output_frames_.empty());
// After a decode error occurred, all following decodes will return
// kDecodeError.
EXPECT_EQ(VideoDecoder::kDecodeError, Decode(i_frame_buffer_));
EXPECT_TRUE(output_frames_.empty());
}
// A corrupt frame followed by an EOS buffer should raise a decode error.
TEST_F(FFmpegVideoDecoderTest, DecodeFrame_DecodeErrorAtEndOfStream) {
Initialize();
EXPECT_EQ(VideoDecoder::kDecodeError,
DecodeSingleFrame(corrupt_i_frame_buffer_));
}
// Decode |i_frame_buffer_| and then a frame with a larger width and verify
// the output size was adjusted.
TEST_F(FFmpegVideoDecoderTest, DecodeFrame_LargerWidth) {
DecodeIFrameThenTestFile("vp8-I-frame-640x240", 640, 240);
}
// Decode |i_frame_buffer_| and then a frame with a smaller width and verify
// the output size was adjusted.
TEST_F(FFmpegVideoDecoderTest, DecodeFrame_SmallerWidth) {
DecodeIFrameThenTestFile("vp8-I-frame-160x240", 160, 240);
}
// Decode |i_frame_buffer_| and then a frame with a larger height and verify
// the output size was adjusted.
TEST_F(FFmpegVideoDecoderTest, DecodeFrame_LargerHeight) {
DecodeIFrameThenTestFile("vp8-I-frame-320x480", 320, 480);
}
// Decode |i_frame_buffer_| and then a frame with a smaller height and verify
// the output size was adjusted.
TEST_F(FFmpegVideoDecoderTest, DecodeFrame_SmallerHeight) {
DecodeIFrameThenTestFile("vp8-I-frame-320x120", 320, 120);
}
// Test resetting when decoder has initialized but not decoded.
TEST_F(FFmpegVideoDecoderTest, Reset_Initialized) {
Initialize();
Reset();
}
// Test resetting when decoder has decoded single frame.
TEST_F(FFmpegVideoDecoderTest, Reset_Decoding) {
Initialize();
EnterDecodingState();
Reset();
}
// Test resetting when decoder has hit end of stream.
TEST_F(FFmpegVideoDecoderTest, Reset_EndOfStream) {
Initialize();
EnterDecodingState();
EnterEndOfStreamState();
Reset();
}
// Test destruction when decoder has initialized but not decoded.
TEST_F(FFmpegVideoDecoderTest, Destroy_Initialized) {
Initialize();
Destroy();
}
// Test destruction when decoder has decoded single frame.
TEST_F(FFmpegVideoDecoderTest, Destroy_Decoding) {
Initialize();
EnterDecodingState();
Destroy();
}
// Test destruction when decoder has hit end of stream.
TEST_F(FFmpegVideoDecoderTest, Destroy_EndOfStream) {
Initialize();
EnterDecodingState();
EnterEndOfStreamState();
Destroy();
}
} // namespace media