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