| // 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/ffmpeg/ffmpeg_common.h" |
| #include "media/filters/ffmpeg_glue.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, size_t(size_t 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_(NULL) {} |
| |
| static void SetUpTestCase() { |
| // Singleton should initialize FFmpeg. |
| CHECK(FFmpegGlue::GetInstance()); |
| } |
| |
| virtual void SetUp() { |
| // Assign our static copy of URLProtocol for the rest of the tests. |
| protocol_ = FFmpegGlue::url_protocol(); |
| CHECK(protocol_); |
| } |
| |
| MOCK_METHOD1(CheckPoint, void(int val)); |
| |
| // Helper to open a URLContext pointing to the given mocked protocol. |
| // Callers are expected to close the context at the end of their test. |
| virtual void OpenContext(MockProtocol* protocol, URLContext* context) { |
| // IsStreaming() is called when opening. |
| EXPECT_CALL(*protocol, IsStreaming()).WillOnce(Return(true)); |
| |
| // Add the protocol to the glue layer and open a context. |
| std::string key = FFmpegGlue::GetInstance()->AddProtocol(protocol); |
| memset(context, 0, sizeof(*context)); |
| EXPECT_EQ(0, protocol_->url_open(context, key.c_str(), 0)); |
| FFmpegGlue::GetInstance()->RemoveProtocol(protocol); |
| } |
| |
| protected: |
| // Fixture members. |
| URLProtocol* protocol_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(FFmpegGlueTest); |
| }; |
| |
| TEST_F(FFmpegGlueTest, InitializeFFmpeg) { |
| // Make sure URLProtocol was filled out correctly. |
| EXPECT_STREQ("http", protocol_->name); |
| EXPECT_TRUE(protocol_->url_close); |
| EXPECT_TRUE(protocol_->url_open); |
| EXPECT_TRUE(protocol_->url_read); |
| EXPECT_TRUE(protocol_->url_seek); |
| EXPECT_TRUE(protocol_->url_write); |
| } |
| |
| TEST_F(FFmpegGlueTest, AddRemoveGetProtocol) { |
| // Prepare testing data. |
| FFmpegGlue* glue = FFmpegGlue::GetInstance(); |
| |
| // Create our protocols and add them to the glue layer. |
| scoped_ptr<StrictMock<Destroyable<MockProtocol> > > protocol_a( |
| new StrictMock<Destroyable<MockProtocol> >()); |
| scoped_ptr<StrictMock<Destroyable<MockProtocol> > > protocol_b( |
| new StrictMock<Destroyable<MockProtocol> >()); |
| |
| // Make sure the keys are unique. |
| std::string key_a = glue->AddProtocol(protocol_a.get()); |
| std::string key_b = glue->AddProtocol(protocol_b.get()); |
| EXPECT_EQ(0u, key_a.find("http://")); |
| EXPECT_EQ(0u, key_b.find("http://")); |
| EXPECT_NE(key_a, key_b); |
| |
| // Our keys should return our protocols. |
| FFmpegURLProtocol* protocol_c; |
| FFmpegURLProtocol* protocol_d; |
| glue->GetProtocol(key_a, &protocol_c); |
| glue->GetProtocol(key_b, &protocol_d); |
| EXPECT_EQ(protocol_a.get(), protocol_c); |
| EXPECT_EQ(protocol_b.get(), protocol_d); |
| |
| // Adding the same Protocol should create the same key and not add an extra |
| // reference. |
| std::string key_a2 = glue->AddProtocol(protocol_a.get()); |
| EXPECT_EQ(key_a, key_a2); |
| glue->GetProtocol(key_a2, &protocol_c); |
| EXPECT_EQ(protocol_a.get(), protocol_c); |
| |
| // Removes the protocols then releases our references. They should be |
| // destroyed. |
| InSequence s; |
| EXPECT_CALL(*protocol_a, OnDestroy()); |
| EXPECT_CALL(*protocol_b, OnDestroy()); |
| EXPECT_CALL(*this, CheckPoint(0)); |
| |
| glue->RemoveProtocol(protocol_a.get()); |
| glue->GetProtocol(key_a, &protocol_c); |
| EXPECT_FALSE(protocol_c); |
| glue->GetProtocol(key_b, &protocol_d); |
| EXPECT_EQ(protocol_b.get(), protocol_d); |
| glue->RemoveProtocol(protocol_b.get()); |
| glue->GetProtocol(key_b, &protocol_d); |
| EXPECT_FALSE(protocol_d); |
| protocol_a.reset(); |
| protocol_b.reset(); |
| |
| // Data sources should be deleted by this point. |
| CheckPoint(0); |
| } |
| |
| TEST_F(FFmpegGlueTest, OpenClose) { |
| // Prepare testing data. |
| FFmpegGlue* glue = FFmpegGlue::GetInstance(); |
| |
| // Create our protocol and add them to the glue layer. |
| scoped_ptr<StrictMock<Destroyable<MockProtocol> > > protocol( |
| new StrictMock<Destroyable<MockProtocol> >()); |
| EXPECT_CALL(*protocol, IsStreaming()).WillOnce(Return(true)); |
| std::string key = glue->AddProtocol(protocol.get()); |
| |
| // Prepare FFmpeg URLContext structure. |
| URLContext context; |
| memset(&context, 0, sizeof(context)); |
| |
| // Test opening a URLContext with a protocol that doesn't exist. |
| EXPECT_EQ(AVERROR(EIO), protocol_->url_open(&context, "foobar", 0)); |
| |
| // Test opening a URLContext with our protocol. |
| EXPECT_EQ(0, protocol_->url_open(&context, key.c_str(), 0)); |
| EXPECT_EQ(AVIO_FLAG_READ, context.flags); |
| EXPECT_EQ(protocol.get(), context.priv_data); |
| EXPECT_TRUE(context.is_streamed); |
| |
| // We're going to remove references one by one until the last reference is |
| // held by FFmpeg. Once we close the URLContext, the protocol should be |
| // destroyed. |
| InSequence s; |
| EXPECT_CALL(*this, CheckPoint(0)); |
| EXPECT_CALL(*this, CheckPoint(1)); |
| EXPECT_CALL(*protocol, OnDestroy()); |
| EXPECT_CALL(*this, CheckPoint(2)); |
| |
| // Remove the protocol from the glue layer, releasing a reference. |
| glue->RemoveProtocol(protocol.get()); |
| CheckPoint(0); |
| |
| // Remove our own reference -- URLContext should maintain a reference. |
| CheckPoint(1); |
| protocol.reset(); |
| |
| // Close the URLContext, which should release the final reference. |
| EXPECT_EQ(0, protocol_->url_close(&context)); |
| CheckPoint(2); |
| } |
| |
| TEST_F(FFmpegGlueTest, Write) { |
| scoped_ptr<StrictMock<MockProtocol> > protocol( |
| new StrictMock<MockProtocol>()); |
| URLContext context; |
| OpenContext(protocol.get(), &context); |
| |
| const int kBufferSize = 16; |
| uint8 buffer[kBufferSize]; |
| |
| // Writing should always fail and never call the protocol. |
| EXPECT_EQ(AVERROR(EIO), protocol_->url_write(&context, NULL, 0)); |
| EXPECT_EQ(AVERROR(EIO), protocol_->url_write(&context, buffer, 0)); |
| EXPECT_EQ(AVERROR(EIO), protocol_->url_write(&context, buffer, kBufferSize)); |
| |
| // Destroy the protocol. |
| protocol_->url_close(&context); |
| } |
| |
| TEST_F(FFmpegGlueTest, Read) { |
| scoped_ptr<StrictMock<MockProtocol> > protocol( |
| new StrictMock<MockProtocol>()); |
| URLContext context; |
| OpenContext(protocol.get(), &context); |
| |
| 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, protocol_->url_read(&context, buffer, 0)); |
| EXPECT_EQ(kBufferSize, protocol_->url_read(&context, buffer, kBufferSize)); |
| EXPECT_EQ(AVERROR(EIO), protocol_->url_read(&context, buffer, kBufferSize)); |
| |
| // Destroy the protocol. |
| protocol_->url_close(&context); |
| } |
| |
| TEST_F(FFmpegGlueTest, Seek) { |
| scoped_ptr<StrictMock<MockProtocol> > protocol( |
| new StrictMock<MockProtocol>()); |
| URLContext context; |
| OpenContext(protocol.get(), &context); |
| |
| // 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), protocol_->url_seek(&context, -16, SEEK_SET)); |
| EXPECT_EQ(8, protocol_->url_seek(&context, 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), protocol_->url_seek(&context, 8, SEEK_CUR)); |
| EXPECT_EQ(AVERROR(EIO), protocol_->url_seek(&context, 8, SEEK_CUR)); |
| EXPECT_EQ(16, protocol_->url_seek(&context, 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), protocol_->url_seek(&context, -8, SEEK_END)); |
| EXPECT_EQ(AVERROR(EIO), protocol_->url_seek(&context, -8, SEEK_END)); |
| EXPECT_EQ(8, protocol_->url_seek(&context, -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), protocol_->url_seek(&context, 0, AVSEEK_SIZE)); |
| EXPECT_EQ(16, protocol_->url_seek(&context, 0, AVSEEK_SIZE)); |
| |
| // Destroy the protocol. |
| protocol_->url_close(&context); |
| } |
| |
| TEST_F(FFmpegGlueTest, Destroy) { |
| // Create our protocol and add them to the glue layer. |
| scoped_ptr<StrictMock<Destroyable<MockProtocol> > > protocol( |
| new StrictMock<Destroyable<MockProtocol> >()); |
| std::string key = FFmpegGlue::GetInstance()->AddProtocol(protocol.get()); |
| |
| // We should expect the protocol to get destroyed when the unit test |
| // exits. |
| InSequence s; |
| EXPECT_CALL(*this, CheckPoint(0)); |
| EXPECT_CALL(*protocol, OnDestroy()); |
| |
| // Remove our own reference, we shouldn't be destroyed yet. |
| CheckPoint(0); |
| protocol.reset(); |
| |
| // ~FFmpegGlue() will be called when this unit test finishes execution. By |
| // leaving something inside FFmpegGlue's map we get to test our cleanup code. |
| } |
| |
| } // namespace media |