1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/filters/ffmpeg_glue.h"
11 #include "base/check.h"
12 #include "base/test/metrics/histogram_tester.h"
13 #include "base/test/scoped_feature_list.h"
14 #include "build/chromeos_buildflags.h"
15 #include "media/base/container_names.h"
16 #include "media/base/media_switches.h"
17 #include "media/base/mock_filters.h"
18 #include "media/base/test_data_util.h"
19 #include "media/ffmpeg/ffmpeg_common.h"
20 #include "media/ffmpeg/ffmpeg_deleters.h"
21 #include "media/filters/in_memory_url_protocol.h"
22 #include "testing/gtest/include/gtest/gtest.h"
25 using ::testing::DoAll;
26 using ::testing::InSequence;
27 using ::testing::Return;
28 using ::testing::SetArgPointee;
29 using ::testing::StrictMock;
33 class MockProtocol : public FFmpegURLProtocol {
35 MockProtocol() = default;
37 MockProtocol(const MockProtocol&) = delete;
38 MockProtocol& operator=(const MockProtocol&) = delete;
40 virtual ~MockProtocol() = default;
42 MOCK_METHOD2(Read, int(int size, uint8_t* data));
43 MOCK_METHOD1(GetPosition, bool(int64_t* position_out));
44 MOCK_METHOD1(SetPosition, bool(int64_t position));
45 MOCK_METHOD1(GetSize, bool(int64_t* size_out));
46 MOCK_METHOD0(IsStreaming, bool());
49 class FFmpegGlueTest : public ::testing::Test {
51 FFmpegGlueTest() : protocol_(std::make_unique<StrictMock<MockProtocol>>()) {
52 // IsStreaming() is called when opening.
53 EXPECT_CALL(*protocol_.get(), IsStreaming()).WillOnce(Return(true));
54 glue_ = std::make_unique<FFmpegGlue>(protocol_.get());
55 CHECK(glue_->format_context());
56 CHECK(glue_->format_context()->pb);
59 FFmpegGlueTest(const FFmpegGlueTest&) = delete;
60 FFmpegGlueTest& operator=(const FFmpegGlueTest&) = delete;
62 ~FFmpegGlueTest() override {
63 // Ensure |glue_| and |protocol_| are still alive.
65 CHECK(protocol_.get());
67 // |protocol_| should outlive |glue_|, so ensure it's destructed first.
71 int ReadPacket(int size, uint8_t* data) {
72 return glue_->format_context()->pb->read_packet(protocol_.get(), data,
76 int64_t Seek(int64_t offset, int whence) {
77 return glue_->format_context()->pb->seek(protocol_.get(), offset, whence);
81 std::unique_ptr<FFmpegGlue> glue_;
82 std::unique_ptr<StrictMock<MockProtocol>> protocol_;
85 class FFmpegGlueDestructionTest : public ::testing::Test {
87 FFmpegGlueDestructionTest() = default;
89 void Initialize(const char* filename) {
90 data_ = ReadTestDataFile(filename);
91 protocol_ = std::make_unique<InMemoryUrlProtocol>(
92 data_->data(), data_->data_size(), false);
93 glue_ = std::make_unique<FFmpegGlue>(protocol_.get());
94 CHECK(glue_->format_context());
95 CHECK(glue_->format_context()->pb);
98 FFmpegGlueDestructionTest(const FFmpegGlueDestructionTest&) = delete;
99 FFmpegGlueDestructionTest& operator=(const FFmpegGlueDestructionTest&) =
102 ~FFmpegGlueDestructionTest() override {
103 // Ensure Initialize() was called.
105 CHECK(protocol_.get());
107 // |glue_| should be destroyed before |protocol_|.
110 // |protocol_| should be destroyed before |data_|.
116 std::unique_ptr<FFmpegGlue> glue_;
119 std::unique_ptr<InMemoryUrlProtocol> protocol_;
120 scoped_refptr<DecoderBuffer> data_;
123 // Tests that ensure we are using the correct AVInputFormat name given by ffmpeg
124 // for supported containers.
125 class FFmpegGlueContainerTest : public FFmpegGlueDestructionTest {
127 FFmpegGlueContainerTest() = default;
129 FFmpegGlueContainerTest(const FFmpegGlueContainerTest&) = delete;
130 FFmpegGlueContainerTest& operator=(const FFmpegGlueContainerTest&) = delete;
132 ~FFmpegGlueContainerTest() override = default;
135 void InitializeAndOpen(const char* filename) {
136 Initialize(filename);
137 ASSERT_TRUE(glue_->OpenContext());
140 void ExpectContainer(container_names::MediaContainerName container) {
141 histogram_tester_.ExpectUniqueSample("Media.DetectedContainer", container,
146 base::HistogramTester histogram_tester_;
149 // Ensure writing has been disabled.
150 TEST_F(FFmpegGlueTest, Write) {
151 ASSERT_FALSE(glue_->format_context()->pb->write_packet);
152 ASSERT_FALSE(glue_->format_context()->pb->write_flag);
155 // Test both successful and unsuccessful reads pass through correctly.
156 TEST_F(FFmpegGlueTest, Read) {
157 const int kBufferSize = 16;
158 uint8_t buffer[kBufferSize];
160 // Reads are for the most part straight-through calls to Read().
162 EXPECT_CALL(*protocol_, Read(0, buffer))
163 .WillOnce(Return(0));
164 EXPECT_CALL(*protocol_, Read(kBufferSize, buffer))
165 .WillOnce(Return(kBufferSize));
166 EXPECT_CALL(*protocol_, Read(kBufferSize, buffer))
167 .WillOnce(Return(AVERROR(EIO)));
169 EXPECT_EQ(0, ReadPacket(0, buffer));
170 EXPECT_EQ(kBufferSize, ReadPacket(kBufferSize, buffer));
171 EXPECT_EQ(AVERROR(EIO), ReadPacket(kBufferSize, buffer));
174 // Test a variety of seek operations.
175 TEST_F(FFmpegGlueTest, Seek) {
176 // SEEK_SET should be a straight-through call to SetPosition(), which when
177 // successful will return the result from GetPosition().
179 EXPECT_CALL(*protocol_, SetPosition(-16))
180 .WillOnce(Return(false));
182 EXPECT_CALL(*protocol_, SetPosition(16))
183 .WillOnce(Return(true));
184 EXPECT_CALL(*protocol_, GetPosition(_))
185 .WillOnce(DoAll(SetArgPointee<0>(8), Return(true)));
187 EXPECT_EQ(AVERROR(EIO), Seek(-16, SEEK_SET));
188 EXPECT_EQ(8, Seek(16, SEEK_SET));
190 // SEEK_CUR should call GetPosition() first, and if it succeeds add the offset
191 // to the result then call SetPosition()+GetPosition().
192 EXPECT_CALL(*protocol_, GetPosition(_))
193 .WillOnce(Return(false));
195 EXPECT_CALL(*protocol_, GetPosition(_))
196 .WillOnce(DoAll(SetArgPointee<0>(8), Return(true)));
197 EXPECT_CALL(*protocol_, SetPosition(16))
198 .WillOnce(Return(false));
200 EXPECT_CALL(*protocol_, GetPosition(_))
201 .WillOnce(DoAll(SetArgPointee<0>(8), Return(true)));
202 EXPECT_CALL(*protocol_, SetPosition(16))
203 .WillOnce(Return(true));
204 EXPECT_CALL(*protocol_, GetPosition(_))
205 .WillOnce(DoAll(SetArgPointee<0>(16), Return(true)));
207 EXPECT_EQ(AVERROR(EIO), Seek(8, SEEK_CUR));
208 EXPECT_EQ(AVERROR(EIO), Seek(8, SEEK_CUR));
209 EXPECT_EQ(16, Seek(8, SEEK_CUR));
211 // SEEK_END should call GetSize() first, and if it succeeds add the offset
212 // to the result then call SetPosition()+GetPosition().
213 EXPECT_CALL(*protocol_, GetSize(_))
214 .WillOnce(Return(false));
216 EXPECT_CALL(*protocol_, GetSize(_))
217 .WillOnce(DoAll(SetArgPointee<0>(16), Return(true)));
218 EXPECT_CALL(*protocol_, SetPosition(8))
219 .WillOnce(Return(false));
221 EXPECT_CALL(*protocol_, GetSize(_))
222 .WillOnce(DoAll(SetArgPointee<0>(16), Return(true)));
223 EXPECT_CALL(*protocol_, SetPosition(8))
224 .WillOnce(Return(true));
225 EXPECT_CALL(*protocol_, GetPosition(_))
226 .WillOnce(DoAll(SetArgPointee<0>(8), Return(true)));
228 EXPECT_EQ(AVERROR(EIO), Seek(-8, SEEK_END));
229 EXPECT_EQ(AVERROR(EIO), Seek(-8, SEEK_END));
230 EXPECT_EQ(8, Seek(-8, SEEK_END));
232 // AVSEEK_SIZE should be a straight-through call to GetSize().
233 EXPECT_CALL(*protocol_, GetSize(_))
234 .WillOnce(Return(false));
236 EXPECT_CALL(*protocol_, GetSize(_))
237 .WillOnce(DoAll(SetArgPointee<0>(16), Return(true)));
239 EXPECT_EQ(AVERROR(EIO), Seek(0, AVSEEK_SIZE));
240 EXPECT_EQ(16, Seek(0, AVSEEK_SIZE));
243 // Ensure destruction release the appropriate resources when OpenContext() is
245 TEST_F(FFmpegGlueDestructionTest, WithoutOpen) {
246 Initialize("ten_byte_file");
249 // Ensure destruction releases the appropriate resources when
250 // avformat_open_input() fails.
251 TEST_F(FFmpegGlueDestructionTest, WithOpenFailure) {
252 Initialize("ten_byte_file");
253 ASSERT_FALSE(glue_->OpenContext());
256 // Ensure destruction release the appropriate resources when OpenContext() is
257 // called, but no streams have been opened.
258 TEST_F(FFmpegGlueDestructionTest, WithOpenNoStreams) {
259 Initialize("no_streams.webm");
260 ASSERT_TRUE(glue_->OpenContext());
263 // Ensure destruction release the appropriate resources when OpenContext() is
264 // called and streams exist.
265 TEST_F(FFmpegGlueDestructionTest, WithOpenWithStreams) {
266 Initialize("bear-320x240.webm");
267 ASSERT_TRUE(glue_->OpenContext());
270 // Ensure destruction release the appropriate resources when OpenContext() is
271 // called and streams have been opened. This now requires user of FFmpegGlue to
272 // ensure any allocated AVCodecContext is closed prior to ~FFmpegGlue().
273 TEST_F(FFmpegGlueDestructionTest, WithOpenWithOpenStreams) {
274 Initialize("bear-320x240.webm");
275 ASSERT_TRUE(glue_->OpenContext());
276 ASSERT_GT(glue_->format_context()->nb_streams, 0u);
278 // Use ScopedPtrAVFreeContext to ensure |context| is closed, and use scoping
279 // and ordering to ensure |context| is destructed before |glue_|.
280 // Pick the audio stream (1) so this works when the ffmpeg video decoders are
282 std::unique_ptr<AVCodecContext, ScopedPtrAVFreeContext> context(
283 AVStreamToAVCodecContext(glue_->format_context()->streams[1]));
284 ASSERT_NE(nullptr, context.get());
285 ASSERT_EQ(0, avcodec_open2(context.get(),
286 avcodec_find_decoder(context->codec_id), nullptr));
289 TEST_F(FFmpegGlueContainerTest, OGG) {
290 InitializeAndOpen("sfx.ogg");
291 ExpectContainer(container_names::MediaContainerName::kContainerOgg);
294 TEST_F(FFmpegGlueContainerTest, WEBM) {
295 InitializeAndOpen("sfx-opus-441.webm");
296 ExpectContainer(container_names::MediaContainerName::kContainerWEBM);
299 TEST_F(FFmpegGlueContainerTest, FLAC) {
300 InitializeAndOpen("sfx.flac");
301 ExpectContainer(container_names::MediaContainerName::kContainerFLAC);
304 TEST_F(FFmpegGlueContainerTest, WAV) {
305 InitializeAndOpen("sfx_s16le.wav");
306 ExpectContainer(container_names::MediaContainerName::kContainerWAV);
309 TEST_F(FFmpegGlueContainerTest, MP3) {
310 InitializeAndOpen("sfx.mp3");
311 ExpectContainer(container_names::MediaContainerName::kContainerMP3);
314 #if BUILDFLAG(USE_PROPRIETARY_CODECS)
315 TEST_F(FFmpegGlueContainerTest, MOV) {
316 InitializeAndOpen("sfx.m4a");
317 ExpectContainer(container_names::MediaContainerName::kContainerMOV);
320 TEST_F(FFmpegGlueContainerTest, AAC) {
321 InitializeAndOpen("sfx.adts");
322 ExpectContainer(container_names::MediaContainerName::kContainerAAC);
325 #if BUILDFLAG(IS_CHROMEOS)
326 TEST_F(FFmpegGlueContainerTest, AVI) {
327 base::test::ScopedFeatureList scoped_enable(kCrOSLegacyMediaFormats);
328 InitializeAndOpen("bear.avi");
329 ExpectContainer(container_names::MediaContainerName::kContainerAVI);
331 #endif // BUILDFLAG(IS_CHROMEOS)
332 #endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
334 // Probe something unsupported to ensure we fall back to the our internal guess.
335 TEST_F(FFmpegGlueContainerTest, FLV) {
336 Initialize("bear.flv");
337 ASSERT_FALSE(glue_->OpenContext());
338 ExpectContainer(container_names::MediaContainerName::kContainerFLV);