| // 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 "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "media/base/mock_filters.h" |
| #include "media/base/test_data_util.h" |
| #include "media/ffmpeg/ffmpeg_common.h" |
| #include "media/filters/ffmpeg_glue.h" |
| #include "media/filters/in_memory_url_protocol.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::DoAll; |
| using ::testing::InSequence; |
| using ::testing::Return; |
| using ::testing::SetArgumentPointee; |
| using ::testing::StrictMock; |
| |
| namespace media { |
| |
| class MockProtocol : public FFmpegURLProtocol { |
| public: |
| MockProtocol() {} |
| |
| MOCK_METHOD2(Read, int(int size, uint8* data)); |
| MOCK_METHOD1(GetPosition, bool(int64* position_out)); |
| MOCK_METHOD1(SetPosition, bool(int64 position)); |
| MOCK_METHOD1(GetSize, bool(int64* size_out)); |
| MOCK_METHOD0(IsStreaming, bool()); |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockProtocol); |
| }; |
| |
| class FFmpegGlueTest : public ::testing::Test { |
| public: |
| FFmpegGlueTest() |
| : protocol_(new StrictMock<MockProtocol>()) { |
| // IsStreaming() is called when opening. |
| EXPECT_CALL(*protocol_.get(), IsStreaming()).WillOnce(Return(true)); |
| glue_.reset(new FFmpegGlue(protocol_.get())); |
| CHECK(glue_->format_context()); |
| CHECK(glue_->format_context()->pb); |
| } |
| |
| virtual ~FFmpegGlueTest() { |
| // Ensure |glue_| and |protocol_| are still alive. |
| CHECK(glue_.get()); |
| CHECK(protocol_.get()); |
| |
| // |protocol_| should outlive |glue_|, so ensure it's destructed first. |
| glue_.reset(); |
| } |
| |
| int ReadPacket(int size, uint8* data) { |
| return glue_->format_context()->pb->read_packet( |
| protocol_.get(), data, size); |
| } |
| |
| int64 Seek(int64 offset, int whence) { |
| return glue_->format_context()->pb->seek(protocol_.get(), offset, whence); |
| } |
| |
| protected: |
| scoped_ptr<FFmpegGlue> glue_; |
| scoped_ptr< StrictMock<MockProtocol> > protocol_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(FFmpegGlueTest); |
| }; |
| |
| class FFmpegGlueDestructionTest : public ::testing::Test { |
| public: |
| FFmpegGlueDestructionTest() {} |
| |
| void Initialize(const char* filename) { |
| data_ = ReadTestDataFile(filename); |
| protocol_.reset(new InMemoryUrlProtocol( |
| data_->GetData(), data_->GetDataSize(), false)); |
| glue_.reset(new FFmpegGlue(protocol_.get())); |
| CHECK(glue_->format_context()); |
| CHECK(glue_->format_context()->pb); |
| } |
| |
| virtual ~FFmpegGlueDestructionTest() { |
| // Ensure Initialize() was called. |
| CHECK(glue_.get()); |
| CHECK(protocol_.get()); |
| |
| // |glue_| should be destroyed before |protocol_|. |
| glue_.reset(); |
| |
| // |protocol_| should be destroyed before |data_|. |
| protocol_.reset(); |
| data_ = NULL; |
| } |
| |
| protected: |
| scoped_ptr<FFmpegGlue> glue_; |
| |
| private: |
| scoped_ptr<InMemoryUrlProtocol> protocol_; |
| scoped_refptr<DecoderBuffer> data_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FFmpegGlueDestructionTest); |
| }; |
| |
| // Ensure writing has been disabled. |
| TEST_F(FFmpegGlueTest, Write) { |
| ASSERT_FALSE(glue_->format_context()->pb->write_packet); |
| ASSERT_FALSE(glue_->format_context()->pb->write_flag); |
| } |
| |
| // Test both successful and unsuccessful reads pass through correctly. |
| TEST_F(FFmpegGlueTest, Read) { |
| const int kBufferSize = 16; |
| uint8 buffer[kBufferSize]; |
| |
| // Reads are for the most part straight-through calls to Read(). |
| InSequence s; |
| EXPECT_CALL(*protocol_, Read(0, buffer)) |
| .WillOnce(Return(0)); |
| EXPECT_CALL(*protocol_, Read(kBufferSize, buffer)) |
| .WillOnce(Return(kBufferSize)); |
| EXPECT_CALL(*protocol_, Read(kBufferSize, buffer)) |
| .WillOnce(Return(DataSource::kReadError)); |
| |
| EXPECT_EQ(0, ReadPacket(0, buffer)); |
| EXPECT_EQ(kBufferSize, ReadPacket(kBufferSize, buffer)); |
| EXPECT_EQ(AVERROR(EIO), ReadPacket(kBufferSize, buffer)); |
| } |
| |
| // Test a variety of seek operations. |
| TEST_F(FFmpegGlueTest, Seek) { |
| // SEEK_SET should be a straight-through call to SetPosition(), which when |
| // successful will return the result from GetPosition(). |
| InSequence s; |
| EXPECT_CALL(*protocol_, SetPosition(-16)) |
| .WillOnce(Return(false)); |
| |
| EXPECT_CALL(*protocol_, SetPosition(16)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(*protocol_, GetPosition(_)) |
| .WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true))); |
| |
| EXPECT_EQ(AVERROR(EIO), Seek(-16, SEEK_SET)); |
| EXPECT_EQ(8, Seek(16, SEEK_SET)); |
| |
| // SEEK_CUR should call GetPosition() first, and if it succeeds add the offset |
| // to the result then call SetPosition()+GetPosition(). |
| EXPECT_CALL(*protocol_, GetPosition(_)) |
| .WillOnce(Return(false)); |
| |
| EXPECT_CALL(*protocol_, GetPosition(_)) |
| .WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true))); |
| EXPECT_CALL(*protocol_, SetPosition(16)) |
| .WillOnce(Return(false)); |
| |
| EXPECT_CALL(*protocol_, GetPosition(_)) |
| .WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true))); |
| EXPECT_CALL(*protocol_, SetPosition(16)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(*protocol_, GetPosition(_)) |
| .WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true))); |
| |
| EXPECT_EQ(AVERROR(EIO), Seek(8, SEEK_CUR)); |
| EXPECT_EQ(AVERROR(EIO), Seek(8, SEEK_CUR)); |
| EXPECT_EQ(16, Seek(8, SEEK_CUR)); |
| |
| // SEEK_END should call GetSize() first, and if it succeeds add the offset |
| // to the result then call SetPosition()+GetPosition(). |
| EXPECT_CALL(*protocol_, GetSize(_)) |
| .WillOnce(Return(false)); |
| |
| EXPECT_CALL(*protocol_, GetSize(_)) |
| .WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true))); |
| EXPECT_CALL(*protocol_, SetPosition(8)) |
| .WillOnce(Return(false)); |
| |
| EXPECT_CALL(*protocol_, GetSize(_)) |
| .WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true))); |
| EXPECT_CALL(*protocol_, SetPosition(8)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(*protocol_, GetPosition(_)) |
| .WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true))); |
| |
| EXPECT_EQ(AVERROR(EIO), Seek(-8, SEEK_END)); |
| EXPECT_EQ(AVERROR(EIO), Seek(-8, SEEK_END)); |
| EXPECT_EQ(8, Seek(-8, SEEK_END)); |
| |
| // AVSEEK_SIZE should be a straight-through call to GetSize(). |
| EXPECT_CALL(*protocol_, GetSize(_)) |
| .WillOnce(Return(false)); |
| |
| EXPECT_CALL(*protocol_, GetSize(_)) |
| .WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true))); |
| |
| EXPECT_EQ(AVERROR(EIO), Seek(0, AVSEEK_SIZE)); |
| EXPECT_EQ(16, Seek(0, AVSEEK_SIZE)); |
| } |
| |
| // Ensure destruction release the appropriate resources when OpenContext() is |
| // never called. |
| TEST_F(FFmpegGlueDestructionTest, WithoutOpen) { |
| Initialize("ten_byte_file"); |
| } |
| |
| // Ensure destruction releases the appropriate resources when |
| // avformat_open_input() fails. |
| TEST_F(FFmpegGlueDestructionTest, WithOpenFailure) { |
| Initialize("ten_byte_file"); |
| ASSERT_FALSE(glue_->OpenContext()); |
| } |
| |
| // Ensure destruction release the appropriate resources when OpenContext() is |
| // called, but no streams have been opened. |
| TEST_F(FFmpegGlueDestructionTest, WithOpenNoStreams) { |
| Initialize("no_streams.webm"); |
| ASSERT_TRUE(glue_->OpenContext()); |
| } |
| |
| // Ensure destruction release the appropriate resources when OpenContext() is |
| // called and streams exist. |
| TEST_F(FFmpegGlueDestructionTest, WithOpenWithStreams) { |
| Initialize("bear-320x240.webm"); |
| ASSERT_TRUE(glue_->OpenContext()); |
| } |
| |
| // Ensure destruction release the appropriate resources when OpenContext() is |
| // called and streams have been opened. |
| TEST_F(FFmpegGlueDestructionTest, WithOpenWithOpenStreams) { |
| Initialize("bear-320x240.webm"); |
| ASSERT_TRUE(glue_->OpenContext()); |
| ASSERT_GT(glue_->format_context()->nb_streams, 0u); |
| |
| AVCodecContext* context = glue_->format_context()->streams[0]->codec; |
| ASSERT_EQ(avcodec_open2( |
| context, avcodec_find_decoder(context->codec_id), NULL), 0); |
| } |
| |
| } // namespace media |