blob: c7e9b2895226de845de9e5de94d23fbaa185bc84 [file] [log] [blame]
// Copyright 2018 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 <stdint.h>
#include <string>
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "media/base/test_data_util.h"
#include "media/gpu/vp8_decoder.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::Return;
namespace media {
namespace {
const std::string kNullFrame = "";
const std::string kIFrame = "vp8-I-frame-320x240";
const std::string kPFrame = "vp8-P-frame-320x240";
const std::string kCorruptFrame = "vp8-corrupt-I-frame";
constexpr gfx::Size kVideoSize(320, 240);
constexpr size_t kRequiredNumOfPictures = 9u;
class MockVP8Accelerator : public VP8Decoder::VP8Accelerator {
public:
MockVP8Accelerator() = default;
MOCK_METHOD0(CreateVP8Picture, scoped_refptr<VP8Picture>());
MOCK_METHOD2(SubmitDecode,
bool(scoped_refptr<VP8Picture> pic,
const Vp8ReferenceFrameVector& reference_frames));
MOCK_METHOD1(OutputPicture, bool(scoped_refptr<VP8Picture> pic));
};
// Test VP8Decoder by feeding different VP8 frame sequences and making sure it
// behaves as expected.
class VP8DecoderTest : public ::testing::Test {
public:
VP8DecoderTest() = default;
void SetUp() override;
AcceleratedVideoDecoder::DecodeResult Decode(std::string input_frame_file);
protected:
void SkipFrame() { bitstream_id_++; }
void CompleteToDecodeFirstIFrame();
std::unique_ptr<VP8Decoder> decoder_;
MockVP8Accelerator* accelerator_ = nullptr;
private:
void DecodeFirstIFrame();
int32_t bitstream_id_ = 0;
};
void VP8DecoderTest::SetUp() {
auto mock_accelerator = std::make_unique<MockVP8Accelerator>();
accelerator_ = mock_accelerator.get();
decoder_.reset(new VP8Decoder(std::move(mock_accelerator)));
// Sets default behaviors for mock methods for convenience.
ON_CALL(*accelerator_, CreateVP8Picture())
.WillByDefault(Return(new VP8Picture()));
ON_CALL(*accelerator_, SubmitDecode(_, _)).WillByDefault(Return(true));
ON_CALL(*accelerator_, OutputPicture(_)).WillByDefault(Return(true));
DecodeFirstIFrame();
}
void VP8DecoderTest::DecodeFirstIFrame() {
ASSERT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode(kNullFrame));
ASSERT_EQ(AcceleratedVideoDecoder::kConfigChange, Decode(kIFrame));
EXPECT_EQ(kVideoSize, decoder_->GetPicSize());
EXPECT_LE(kRequiredNumOfPictures, decoder_->GetRequiredNumOfPictures());
}
// DecodeFirstIFrame() allocates new surfaces so VP8Decoder::Decode() must be
// called again to complete to decode the first frame.
void VP8DecoderTest::CompleteToDecodeFirstIFrame() {
{
InSequence sequence;
EXPECT_CALL(*accelerator_, CreateVP8Picture());
EXPECT_CALL(*accelerator_, SubmitDecode(_, _));
EXPECT_CALL(*accelerator_, OutputPicture(_));
}
ASSERT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode(kNullFrame));
}
AcceleratedVideoDecoder::DecodeResult VP8DecoderTest::Decode(
std::string input_frame_file) {
std::string bitstream;
scoped_refptr<DecoderBuffer> buffer;
if (!input_frame_file.empty()) {
auto input_file = GetTestDataFilePath(input_frame_file);
EXPECT_TRUE(base::ReadFileToString(input_file, &bitstream));
buffer = DecoderBuffer::CopyFrom(
reinterpret_cast<const uint8_t*>(bitstream.data()), bitstream.size());
EXPECT_NE(buffer.get(), nullptr);
decoder_->SetStream(bitstream_id_++, *buffer);
}
AcceleratedVideoDecoder::DecodeResult result = decoder_->Decode();
if (input_frame_file.empty())
return result;
// Since |buffer| is destroyed in this function, Decode() must consume the
// buffer by this Decode(). That happens if the return value is
// kRanOutOfStreamData, kConfigChange , or kDecodeError (on failure).
EXPECT_TRUE(result ==
AcceleratedVideoDecoder::DecodeResult::kRanOutOfStreamData ||
result == AcceleratedVideoDecoder::DecodeResult::kConfigChange ||
result == AcceleratedVideoDecoder::DecodeResult::kDecodeError);
return result;
}
// Test Cases
TEST_F(VP8DecoderTest, DecodeSingleFrame) {
CompleteToDecodeFirstIFrame();
ASSERT_TRUE(Mock::VerifyAndClearExpectations(accelerator_));
ASSERT_TRUE(decoder_->Flush());
}
TEST_F(VP8DecoderTest, FailCreatePicture) {
EXPECT_CALL(*accelerator_, CreateVP8Picture()).WillOnce(Return(nullptr));
ASSERT_EQ(AcceleratedVideoDecoder::kRanOutOfSurfaces, Decode(kNullFrame));
ASSERT_TRUE(decoder_->Flush());
}
TEST_F(VP8DecoderTest, DecodeCorruptFrame) {
CompleteToDecodeFirstIFrame();
ASSERT_EQ(AcceleratedVideoDecoder::kDecodeError, Decode(kCorruptFrame));
ASSERT_TRUE(Mock::VerifyAndClearExpectations(accelerator_));
ASSERT_TRUE(decoder_->Flush());
}
TEST_F(VP8DecoderTest, DecodeIAndPFrames) {
CompleteToDecodeFirstIFrame();
{
InSequence sequence;
EXPECT_CALL(*accelerator_, CreateVP8Picture());
EXPECT_CALL(*accelerator_, SubmitDecode(_, _));
EXPECT_CALL(*accelerator_, OutputPicture(_));
}
ASSERT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode(kPFrame));
ASSERT_TRUE(Mock::VerifyAndClearExpectations(accelerator_));
ASSERT_TRUE(decoder_->Flush());
}
TEST_F(VP8DecoderTest, DecodeIandMultiplePFrames) {
CompleteToDecodeFirstIFrame();
for (size_t i = 0; i < 5; i++) {
{
InSequence sequence;
EXPECT_CALL(*accelerator_, CreateVP8Picture());
EXPECT_CALL(*accelerator_, SubmitDecode(_, _));
EXPECT_CALL(*accelerator_, OutputPicture(_));
}
ASSERT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode(kPFrame));
}
ASSERT_TRUE(Mock::VerifyAndClearExpectations(accelerator_));
ASSERT_TRUE(decoder_->Flush());
}
TEST_F(VP8DecoderTest, DecodeMultipleIAndPFrames) {
CompleteToDecodeFirstIFrame();
for (size_t i = 0; i < 10; i++) {
{
InSequence sequence;
EXPECT_CALL(*accelerator_, CreateVP8Picture());
EXPECT_CALL(*accelerator_, SubmitDecode(_, _));
EXPECT_CALL(*accelerator_, OutputPicture(_));
}
ASSERT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData,
Decode((i % 3) == 0 ? kIFrame : kPFrame));
}
ASSERT_TRUE(Mock::VerifyAndClearExpectations(accelerator_));
ASSERT_TRUE(decoder_->Flush());
}
TEST_F(VP8DecoderTest, HaveSkippedFrames) {
CompleteToDecodeFirstIFrame();
SkipFrame();
for (size_t i = 0; i < 5; i++) {
// VP8Decoder::Decode() gives up decoding it and returns early.
ASSERT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode(kPFrame));
}
ASSERT_TRUE(Mock::VerifyAndClearExpectations(accelerator_));
ASSERT_TRUE(decoder_->Flush());
}
// Verify that |decoder_| returns kDecodeError if too many kPFrames are received
// while expecting a kIFrame.
TEST_F(VP8DecoderTest, HaveSkippedFramesAtMaxNumOfSizeChangeFailures) {
CompleteToDecodeFirstIFrame();
SkipFrame();
for (size_t i = 0;
i < AcceleratedVideoDecoder::kVPxMaxNumOfSizeChangeFailures; i++) {
ASSERT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode(kPFrame));
}
ASSERT_EQ(AcceleratedVideoDecoder::kDecodeError, Decode(kPFrame));
ASSERT_TRUE(Mock::VerifyAndClearExpectations(accelerator_));
ASSERT_TRUE(decoder_->Flush());
}
// Verify that new kIFrame recovers |decoder_| to decode the frame when the
// previous I frame is missing.
TEST_F(VP8DecoderTest, RecoverFromSkippedFrames) {
CompleteToDecodeFirstIFrame();
SkipFrame();
for (size_t i = 0; i < 5; i++)
ASSERT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode(kPFrame));
// The new I frame recovers to decode it correctly.
{
InSequence sequence;
EXPECT_CALL(*accelerator_, CreateVP8Picture());
EXPECT_CALL(*accelerator_, SubmitDecode(_, _));
EXPECT_CALL(*accelerator_, OutputPicture(_));
}
ASSERT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode(kIFrame));
for (size_t i = 0; i < 5; i++) {
{
InSequence sequence;
EXPECT_CALL(*accelerator_, CreateVP8Picture());
EXPECT_CALL(*accelerator_, SubmitDecode(_, _));
EXPECT_CALL(*accelerator_, OutputPicture(_));
}
ASSERT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode(kPFrame));
}
ASSERT_TRUE(Mock::VerifyAndClearExpectations(accelerator_));
ASSERT_TRUE(decoder_->Flush());
}
} // namespace
} // namespace media