1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "base/logging.h" 6 #include "base/memory/scoped_ptr.h" 7 #include "media/base/mock_filters.h" 8 #include "media/base/test_data_util.h" 9 #include "media/ffmpeg/ffmpeg_common.h" 10 #include "media/filters/ffmpeg_glue.h" 11 #include "media/filters/in_memory_url_protocol.h" 12 #include "testing/gtest/include/gtest/gtest.h" 13 14 using ::testing::_; 15 using ::testing::DoAll; 16 using ::testing::InSequence; 17 using ::testing::Return; 18 using ::testing::SetArgumentPointee; 19 using ::testing::StrictMock; 20 21 namespace media { 22 23 class MockProtocol : public FFmpegURLProtocol { 24 public: 25 MockProtocol() {} 26 27 MOCK_METHOD2(Read, int(int size, uint8* data)); 28 MOCK_METHOD1(GetPosition, bool(int64* position_out)); 29 MOCK_METHOD1(SetPosition, bool(int64 position)); 30 MOCK_METHOD1(GetSize, bool(int64* size_out)); 31 MOCK_METHOD0(IsStreaming, bool()); 32 33 private: 34 DISALLOW_COPY_AND_ASSIGN(MockProtocol); 35 }; 36 37 class FFmpegGlueTest : public ::testing::Test { 38 public: 39 FFmpegGlueTest() 40 : protocol_(new StrictMock<MockProtocol>()) { 41 // IsStreaming() is called when opening. 42 EXPECT_CALL(*protocol_.get(), IsStreaming()).WillOnce(Return(true)); 43 glue_.reset(new FFmpegGlue(protocol_.get())); 44 CHECK(glue_->format_context()); 45 CHECK(glue_->format_context()->pb); 46 } 47 48 virtual ~FFmpegGlueTest() { 49 // Ensure |glue_| and |protocol_| are still alive. 50 CHECK(glue_.get()); 51 CHECK(protocol_.get()); 52 53 // |protocol_| should outlive |glue_|, so ensure it's destructed first. 54 glue_.reset(); 55 } 56 57 int ReadPacket(int size, uint8* data) { 58 return glue_->format_context()->pb->read_packet( 59 protocol_.get(), data, size); 60 } 61 62 int64 Seek(int64 offset, int whence) { 63 return glue_->format_context()->pb->seek(protocol_.get(), offset, whence); 64 } 65 66 protected: 67 scoped_ptr<FFmpegGlue> glue_; 68 scoped_ptr< StrictMock<MockProtocol> > protocol_; 69 70 private: 71 DISALLOW_COPY_AND_ASSIGN(FFmpegGlueTest); 72 }; 73 74 class FFmpegGlueDestructionTest : public ::testing::Test { 75 public: 76 FFmpegGlueDestructionTest() {} 77 78 void Initialize(const char* filename) { 79 data_ = ReadTestDataFile(filename); 80 protocol_.reset(new InMemoryUrlProtocol( 81 data_->data(), data_->data_size(), false)); 82 glue_.reset(new FFmpegGlue(protocol_.get())); 83 CHECK(glue_->format_context()); 84 CHECK(glue_->format_context()->pb); 85 } 86 87 virtual ~FFmpegGlueDestructionTest() { 88 // Ensure Initialize() was called. 89 CHECK(glue_.get()); 90 CHECK(protocol_.get()); 91 92 // |glue_| should be destroyed before |protocol_|. 93 glue_.reset(); 94 95 // |protocol_| should be destroyed before |data_|. 96 protocol_.reset(); 97 data_ = NULL; 98 } 99 100 protected: 101 scoped_ptr<FFmpegGlue> glue_; 102 103 private: 104 scoped_ptr<InMemoryUrlProtocol> protocol_; 105 scoped_refptr<DecoderBuffer> data_; 106 107 DISALLOW_COPY_AND_ASSIGN(FFmpegGlueDestructionTest); 108 }; 109 110 // Ensure writing has been disabled. 111 TEST_F(FFmpegGlueTest, Write) { 112 ASSERT_FALSE(glue_->format_context()->pb->write_packet); 113 ASSERT_FALSE(glue_->format_context()->pb->write_flag); 114 } 115 116 // Test both successful and unsuccessful reads pass through correctly. 117 TEST_F(FFmpegGlueTest, Read) { 118 const int kBufferSize = 16; 119 uint8 buffer[kBufferSize]; 120 121 // Reads are for the most part straight-through calls to Read(). 122 InSequence s; 123 EXPECT_CALL(*protocol_, Read(0, buffer)) 124 .WillOnce(Return(0)); 125 EXPECT_CALL(*protocol_, Read(kBufferSize, buffer)) 126 .WillOnce(Return(kBufferSize)); 127 EXPECT_CALL(*protocol_, Read(kBufferSize, buffer)) 128 .WillOnce(Return(DataSource::kReadError)); 129 130 EXPECT_EQ(0, ReadPacket(0, buffer)); 131 EXPECT_EQ(kBufferSize, ReadPacket(kBufferSize, buffer)); 132 EXPECT_EQ(AVERROR(EIO), ReadPacket(kBufferSize, buffer)); 133 } 134 135 // Test a variety of seek operations. 136 TEST_F(FFmpegGlueTest, Seek) { 137 // SEEK_SET should be a straight-through call to SetPosition(), which when 138 // successful will return the result from GetPosition(). 139 InSequence s; 140 EXPECT_CALL(*protocol_, SetPosition(-16)) 141 .WillOnce(Return(false)); 142 143 EXPECT_CALL(*protocol_, SetPosition(16)) 144 .WillOnce(Return(true)); 145 EXPECT_CALL(*protocol_, GetPosition(_)) 146 .WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true))); 147 148 EXPECT_EQ(AVERROR(EIO), Seek(-16, SEEK_SET)); 149 EXPECT_EQ(8, Seek(16, SEEK_SET)); 150 151 // SEEK_CUR should call GetPosition() first, and if it succeeds add the offset 152 // to the result then call SetPosition()+GetPosition(). 153 EXPECT_CALL(*protocol_, GetPosition(_)) 154 .WillOnce(Return(false)); 155 156 EXPECT_CALL(*protocol_, GetPosition(_)) 157 .WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true))); 158 EXPECT_CALL(*protocol_, SetPosition(16)) 159 .WillOnce(Return(false)); 160 161 EXPECT_CALL(*protocol_, GetPosition(_)) 162 .WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true))); 163 EXPECT_CALL(*protocol_, SetPosition(16)) 164 .WillOnce(Return(true)); 165 EXPECT_CALL(*protocol_, GetPosition(_)) 166 .WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true))); 167 168 EXPECT_EQ(AVERROR(EIO), Seek(8, SEEK_CUR)); 169 EXPECT_EQ(AVERROR(EIO), Seek(8, SEEK_CUR)); 170 EXPECT_EQ(16, Seek(8, SEEK_CUR)); 171 172 // SEEK_END should call GetSize() first, and if it succeeds add the offset 173 // to the result then call SetPosition()+GetPosition(). 174 EXPECT_CALL(*protocol_, GetSize(_)) 175 .WillOnce(Return(false)); 176 177 EXPECT_CALL(*protocol_, GetSize(_)) 178 .WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true))); 179 EXPECT_CALL(*protocol_, SetPosition(8)) 180 .WillOnce(Return(false)); 181 182 EXPECT_CALL(*protocol_, GetSize(_)) 183 .WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true))); 184 EXPECT_CALL(*protocol_, SetPosition(8)) 185 .WillOnce(Return(true)); 186 EXPECT_CALL(*protocol_, GetPosition(_)) 187 .WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true))); 188 189 EXPECT_EQ(AVERROR(EIO), Seek(-8, SEEK_END)); 190 EXPECT_EQ(AVERROR(EIO), Seek(-8, SEEK_END)); 191 EXPECT_EQ(8, Seek(-8, SEEK_END)); 192 193 // AVSEEK_SIZE should be a straight-through call to GetSize(). 194 EXPECT_CALL(*protocol_, GetSize(_)) 195 .WillOnce(Return(false)); 196 197 EXPECT_CALL(*protocol_, GetSize(_)) 198 .WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true))); 199 200 EXPECT_EQ(AVERROR(EIO), Seek(0, AVSEEK_SIZE)); 201 EXPECT_EQ(16, Seek(0, AVSEEK_SIZE)); 202 } 203 204 // Ensure destruction release the appropriate resources when OpenContext() is 205 // never called. 206 TEST_F(FFmpegGlueDestructionTest, WithoutOpen) { 207 Initialize("ten_byte_file"); 208 } 209 210 // Ensure destruction releases the appropriate resources when 211 // avformat_open_input() fails. 212 TEST_F(FFmpegGlueDestructionTest, WithOpenFailure) { 213 Initialize("ten_byte_file"); 214 ASSERT_FALSE(glue_->OpenContext()); 215 } 216 217 // Ensure destruction release the appropriate resources when OpenContext() is 218 // called, but no streams have been opened. 219 TEST_F(FFmpegGlueDestructionTest, WithOpenNoStreams) { 220 Initialize("no_streams.webm"); 221 ASSERT_TRUE(glue_->OpenContext()); 222 } 223 224 // Ensure destruction release the appropriate resources when OpenContext() is 225 // called and streams exist. 226 TEST_F(FFmpegGlueDestructionTest, WithOpenWithStreams) { 227 Initialize("bear-320x240.webm"); 228 ASSERT_TRUE(glue_->OpenContext()); 229 } 230 231 // Ensure destruction release the appropriate resources when OpenContext() is 232 // called and streams have been opened. 233 TEST_F(FFmpegGlueDestructionTest, WithOpenWithOpenStreams) { 234 Initialize("bear-320x240.webm"); 235 ASSERT_TRUE(glue_->OpenContext()); 236 ASSERT_GT(glue_->format_context()->nb_streams, 0u); 237 238 AVCodecContext* context = glue_->format_context()->streams[0]->codec; 239 ASSERT_EQ(avcodec_open2( 240 context, avcodec_find_decoder(context->codec_id), NULL), 0); 241 } 242 243 } // namespace media 244