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