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 "media/filters/ffmpeg_glue.h"
6 
7 #include <stdint.h>
8 
9 #include <memory>
10 
11 #include "base/check.h"
12 #include "base/macros.h"
13 #include "base/test/metrics/histogram_tester.h"
14 #include "build/chromeos_buildflags.h"
15 #include "media/base/container_names.h"
16 #include "media/base/mock_filters.h"
17 #include "media/base/test_data_util.h"
18 #include "media/ffmpeg/ffmpeg_common.h"
19 #include "media/ffmpeg/ffmpeg_deleters.h"
20 #include "media/filters/in_memory_url_protocol.h"
21 #include "testing/gtest/include/gtest/gtest.h"
22 
23 using ::testing::_;
24 using ::testing::DoAll;
25 using ::testing::InSequence;
26 using ::testing::Return;
27 using ::testing::SetArgPointee;
28 using ::testing::StrictMock;
29 
30 namespace media {
31 
32 class MockProtocol : public FFmpegURLProtocol {
33  public:
34   MockProtocol() = default;
35   virtual ~MockProtocol() = default;
36 
37   MOCK_METHOD2(Read, int(int size, uint8_t* data));
38   MOCK_METHOD1(GetPosition, bool(int64_t* position_out));
39   MOCK_METHOD1(SetPosition, bool(int64_t position));
40   MOCK_METHOD1(GetSize, bool(int64_t* size_out));
41   MOCK_METHOD0(IsStreaming, bool());
42 
43  private:
44   DISALLOW_COPY_AND_ASSIGN(MockProtocol);
45 };
46 
47 class FFmpegGlueTest : public ::testing::Test {
48  public:
FFmpegGlueTest()49   FFmpegGlueTest()
50       : protocol_(new StrictMock<MockProtocol>()) {
51     // IsStreaming() is called when opening.
52     EXPECT_CALL(*protocol_.get(), IsStreaming()).WillOnce(Return(true));
53     glue_.reset(new FFmpegGlue(protocol_.get()));
54     CHECK(glue_->format_context());
55     CHECK(glue_->format_context()->pb);
56   }
57 
~FFmpegGlueTest()58   ~FFmpegGlueTest() override {
59     // Ensure |glue_| and |protocol_| are still alive.
60     CHECK(glue_.get());
61     CHECK(protocol_.get());
62 
63     // |protocol_| should outlive |glue_|, so ensure it's destructed first.
64     glue_.reset();
65   }
66 
ReadPacket(int size,uint8_t * data)67   int ReadPacket(int size, uint8_t* data) {
68     return glue_->format_context()->pb->read_packet(protocol_.get(), data,
69                                                     size);
70   }
71 
Seek(int64_t offset,int whence)72   int64_t Seek(int64_t offset, int whence) {
73     return glue_->format_context()->pb->seek(protocol_.get(), offset, whence);
74   }
75 
76  protected:
77   std::unique_ptr<FFmpegGlue> glue_;
78   std::unique_ptr<StrictMock<MockProtocol>> protocol_;
79 
80  private:
81   DISALLOW_COPY_AND_ASSIGN(FFmpegGlueTest);
82 };
83 
84 class FFmpegGlueDestructionTest : public ::testing::Test {
85  public:
86   FFmpegGlueDestructionTest() = default;
87 
Initialize(const char * filename)88   void Initialize(const char* filename) {
89     data_ = ReadTestDataFile(filename);
90     protocol_.reset(new InMemoryUrlProtocol(
91         data_->data(), data_->data_size(), false));
92     glue_.reset(new FFmpegGlue(protocol_.get()));
93     CHECK(glue_->format_context());
94     CHECK(glue_->format_context()->pb);
95   }
96 
~FFmpegGlueDestructionTest()97   ~FFmpegGlueDestructionTest() override {
98     // Ensure Initialize() was called.
99     CHECK(glue_.get());
100     CHECK(protocol_.get());
101 
102     // |glue_| should be destroyed before |protocol_|.
103     glue_.reset();
104 
105     // |protocol_| should be destroyed before |data_|.
106     protocol_.reset();
107     data_.reset();
108   }
109 
110  protected:
111   std::unique_ptr<FFmpegGlue> glue_;
112 
113  private:
114   std::unique_ptr<InMemoryUrlProtocol> protocol_;
115   scoped_refptr<DecoderBuffer> data_;
116 
117   DISALLOW_COPY_AND_ASSIGN(FFmpegGlueDestructionTest);
118 };
119 
120 // Tests that ensure we are using the correct AVInputFormat name given by ffmpeg
121 // for supported containers.
122 class FFmpegGlueContainerTest : public FFmpegGlueDestructionTest {
123  public:
124   FFmpegGlueContainerTest() = default;
125   ~FFmpegGlueContainerTest() override = default;
126 
127  protected:
InitializeAndOpen(const char * filename)128   void InitializeAndOpen(const char* filename) {
129     Initialize(filename);
130     ASSERT_TRUE(glue_->OpenContext());
131   }
132 
ExpectContainer(container_names::MediaContainerName container)133   void ExpectContainer(container_names::MediaContainerName container) {
134     histogram_tester_.ExpectUniqueSample("Media.DetectedContainer", container,
135                                          1);
136   }
137 
138  private:
139   base::HistogramTester histogram_tester_;
140   DISALLOW_COPY_AND_ASSIGN(FFmpegGlueContainerTest);
141 };
142 
143 // Ensure writing has been disabled.
TEST_F(FFmpegGlueTest,Write)144 TEST_F(FFmpegGlueTest, Write) {
145   ASSERT_FALSE(glue_->format_context()->pb->write_packet);
146   ASSERT_FALSE(glue_->format_context()->pb->write_flag);
147 }
148 
149 // Test both successful and unsuccessful reads pass through correctly.
TEST_F(FFmpegGlueTest,Read)150 TEST_F(FFmpegGlueTest, Read) {
151   const int kBufferSize = 16;
152   uint8_t buffer[kBufferSize];
153 
154   // Reads are for the most part straight-through calls to Read().
155   InSequence s;
156   EXPECT_CALL(*protocol_, Read(0, buffer))
157       .WillOnce(Return(0));
158   EXPECT_CALL(*protocol_, Read(kBufferSize, buffer))
159       .WillOnce(Return(kBufferSize));
160   EXPECT_CALL(*protocol_, Read(kBufferSize, buffer))
161       .WillOnce(Return(AVERROR(EIO)));
162 
163   EXPECT_EQ(0, ReadPacket(0, buffer));
164   EXPECT_EQ(kBufferSize, ReadPacket(kBufferSize, buffer));
165   EXPECT_EQ(AVERROR(EIO), ReadPacket(kBufferSize, buffer));
166 }
167 
168 // Test a variety of seek operations.
TEST_F(FFmpegGlueTest,Seek)169 TEST_F(FFmpegGlueTest, Seek) {
170   // SEEK_SET should be a straight-through call to SetPosition(), which when
171   // successful will return the result from GetPosition().
172   InSequence s;
173   EXPECT_CALL(*protocol_, SetPosition(-16))
174       .WillOnce(Return(false));
175 
176   EXPECT_CALL(*protocol_, SetPosition(16))
177       .WillOnce(Return(true));
178   EXPECT_CALL(*protocol_, GetPosition(_))
179       .WillOnce(DoAll(SetArgPointee<0>(8), Return(true)));
180 
181   EXPECT_EQ(AVERROR(EIO), Seek(-16, SEEK_SET));
182   EXPECT_EQ(8, Seek(16, SEEK_SET));
183 
184   // SEEK_CUR should call GetPosition() first, and if it succeeds add the offset
185   // to the result then call SetPosition()+GetPosition().
186   EXPECT_CALL(*protocol_, GetPosition(_))
187       .WillOnce(Return(false));
188 
189   EXPECT_CALL(*protocol_, GetPosition(_))
190       .WillOnce(DoAll(SetArgPointee<0>(8), Return(true)));
191   EXPECT_CALL(*protocol_, SetPosition(16))
192       .WillOnce(Return(false));
193 
194   EXPECT_CALL(*protocol_, GetPosition(_))
195       .WillOnce(DoAll(SetArgPointee<0>(8), Return(true)));
196   EXPECT_CALL(*protocol_, SetPosition(16))
197       .WillOnce(Return(true));
198   EXPECT_CALL(*protocol_, GetPosition(_))
199       .WillOnce(DoAll(SetArgPointee<0>(16), Return(true)));
200 
201   EXPECT_EQ(AVERROR(EIO), Seek(8, SEEK_CUR));
202   EXPECT_EQ(AVERROR(EIO), Seek(8, SEEK_CUR));
203   EXPECT_EQ(16, Seek(8, SEEK_CUR));
204 
205   // SEEK_END should call GetSize() first, and if it succeeds add the offset
206   // to the result then call SetPosition()+GetPosition().
207   EXPECT_CALL(*protocol_, GetSize(_))
208       .WillOnce(Return(false));
209 
210   EXPECT_CALL(*protocol_, GetSize(_))
211       .WillOnce(DoAll(SetArgPointee<0>(16), Return(true)));
212   EXPECT_CALL(*protocol_, SetPosition(8))
213       .WillOnce(Return(false));
214 
215   EXPECT_CALL(*protocol_, GetSize(_))
216       .WillOnce(DoAll(SetArgPointee<0>(16), Return(true)));
217   EXPECT_CALL(*protocol_, SetPosition(8))
218       .WillOnce(Return(true));
219   EXPECT_CALL(*protocol_, GetPosition(_))
220       .WillOnce(DoAll(SetArgPointee<0>(8), Return(true)));
221 
222   EXPECT_EQ(AVERROR(EIO), Seek(-8, SEEK_END));
223   EXPECT_EQ(AVERROR(EIO), Seek(-8, SEEK_END));
224   EXPECT_EQ(8, Seek(-8, SEEK_END));
225 
226   // AVSEEK_SIZE should be a straight-through call to GetSize().
227   EXPECT_CALL(*protocol_, GetSize(_))
228       .WillOnce(Return(false));
229 
230   EXPECT_CALL(*protocol_, GetSize(_))
231       .WillOnce(DoAll(SetArgPointee<0>(16), Return(true)));
232 
233   EXPECT_EQ(AVERROR(EIO), Seek(0, AVSEEK_SIZE));
234   EXPECT_EQ(16, Seek(0, AVSEEK_SIZE));
235 }
236 
237 // Ensure destruction release the appropriate resources when OpenContext() is
238 // never called.
TEST_F(FFmpegGlueDestructionTest,WithoutOpen)239 TEST_F(FFmpegGlueDestructionTest, WithoutOpen) {
240   Initialize("ten_byte_file");
241 }
242 
243 // Ensure destruction releases the appropriate resources when
244 // avformat_open_input() fails.
TEST_F(FFmpegGlueDestructionTest,WithOpenFailure)245 TEST_F(FFmpegGlueDestructionTest, WithOpenFailure) {
246   Initialize("ten_byte_file");
247   ASSERT_FALSE(glue_->OpenContext());
248 }
249 
250 // Ensure destruction release the appropriate resources when OpenContext() is
251 // called, but no streams have been opened.
TEST_F(FFmpegGlueDestructionTest,WithOpenNoStreams)252 TEST_F(FFmpegGlueDestructionTest, WithOpenNoStreams) {
253   Initialize("no_streams.webm");
254   ASSERT_TRUE(glue_->OpenContext());
255 }
256 
257 // Ensure destruction release the appropriate resources when OpenContext() is
258 // called and streams exist.
TEST_F(FFmpegGlueDestructionTest,WithOpenWithStreams)259 TEST_F(FFmpegGlueDestructionTest, WithOpenWithStreams) {
260   Initialize("bear-320x240.webm");
261   ASSERT_TRUE(glue_->OpenContext());
262 }
263 
264 // Ensure destruction release the appropriate resources when OpenContext() is
265 // called and streams have been opened. This now requires user of FFmpegGlue to
266 // ensure any allocated AVCodecContext is closed prior to ~FFmpegGlue().
TEST_F(FFmpegGlueDestructionTest,WithOpenWithOpenStreams)267 TEST_F(FFmpegGlueDestructionTest, WithOpenWithOpenStreams) {
268   Initialize("bear-320x240.webm");
269   ASSERT_TRUE(glue_->OpenContext());
270   ASSERT_GT(glue_->format_context()->nb_streams, 0u);
271 
272   // Use ScopedPtrAVFreeContext to ensure |context| is closed, and use scoping
273   // and ordering to ensure |context| is destructed before |glue_|.
274   // Pick the audio stream (1) so this works when the ffmpeg video decoders are
275   // disabled.
276   std::unique_ptr<AVCodecContext, ScopedPtrAVFreeContext> context(
277       AVStreamToAVCodecContext(glue_->format_context()->streams[1]));
278   ASSERT_NE(nullptr, context.get());
279   ASSERT_EQ(0, avcodec_open2(context.get(),
280                              avcodec_find_decoder(context->codec_id), nullptr));
281 }
282 
TEST_F(FFmpegGlueContainerTest,OGG)283 TEST_F(FFmpegGlueContainerTest, OGG) {
284   InitializeAndOpen("sfx.ogg");
285   ExpectContainer(container_names::CONTAINER_OGG);
286 }
287 
TEST_F(FFmpegGlueContainerTest,WEBM)288 TEST_F(FFmpegGlueContainerTest, WEBM) {
289   InitializeAndOpen("sfx-opus-441.webm");
290   ExpectContainer(container_names::CONTAINER_WEBM);
291 }
292 
TEST_F(FFmpegGlueContainerTest,FLAC)293 TEST_F(FFmpegGlueContainerTest, FLAC) {
294   InitializeAndOpen("sfx.flac");
295   ExpectContainer(container_names::CONTAINER_FLAC);
296 }
297 
TEST_F(FFmpegGlueContainerTest,WAV)298 TEST_F(FFmpegGlueContainerTest, WAV) {
299   InitializeAndOpen("sfx_s16le.wav");
300   ExpectContainer(container_names::CONTAINER_WAV);
301 }
302 
TEST_F(FFmpegGlueContainerTest,MP3)303 TEST_F(FFmpegGlueContainerTest, MP3) {
304   InitializeAndOpen("sfx.mp3");
305   ExpectContainer(container_names::CONTAINER_MP3);
306 }
307 
308 #if BUILDFLAG(USE_PROPRIETARY_CODECS)
TEST_F(FFmpegGlueContainerTest,MOV)309 TEST_F(FFmpegGlueContainerTest, MOV) {
310   InitializeAndOpen("sfx.m4a");
311   ExpectContainer(container_names::CONTAINER_MOV);
312 }
313 
TEST_F(FFmpegGlueContainerTest,AAC)314 TEST_F(FFmpegGlueContainerTest, AAC) {
315   InitializeAndOpen("sfx.adts");
316   ExpectContainer(container_names::CONTAINER_AAC);
317 }
318 
319 #if BUILDFLAG(IS_ASH)
TEST_F(FFmpegGlueContainerTest,AVI)320 TEST_F(FFmpegGlueContainerTest, AVI) {
321   InitializeAndOpen("bear.avi");
322   ExpectContainer(container_names::CONTAINER_AVI);
323 }
324 
TEST_F(FFmpegGlueContainerTest,AMR)325 TEST_F(FFmpegGlueContainerTest, AMR) {
326   InitializeAndOpen("bear.amr");
327   ExpectContainer(container_names::CONTAINER_AMR);
328 }
329 #endif  // BUILDFLAG(IS_ASH)
330 #endif  // BUILDFLAG(USE_PROPRIETARY_CODECS)
331 
332 // Probe something unsupported to ensure we fall back to the our internal guess.
TEST_F(FFmpegGlueContainerTest,FLV)333 TEST_F(FFmpegGlueContainerTest, FLV) {
334   Initialize("bear.flv");
335   ASSERT_FALSE(glue_->OpenContext());
336   ExpectContainer(container_names::CONTAINER_FLV);
337 }
338 
339 }  // namespace media
340