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.
5 // ffmpeg_unittests verify that the parts of the FFmpeg API that Chromium uses
6 // function as advertised for each media format that Chromium supports. This
7 // mostly includes stuff like reporting proper timestamps, seeking to
8 // keyframes, and supporting certain features like reordered_opaque.
14 #include "base/base_paths.h"
15 #include "base/file_path.h"
16 #include "base/file_util.h"
17 #include "base/path_service.h"
18 #include "base/perftimer.h"
19 #include "base/string_util.h"
20 #include "base/test/perf_test_suite.h"
21 #include "media/base/media.h"
22 #include "media/ffmpeg/ffmpeg_common.h"
23 #include "media/ffmpeg/file_protocol.h"
24 #include "testing/gtest/include/gtest/gtest.h"
26 int main(int argc
, char** argv
) {
27 return base::PerfTestSuite(argc
, argv
).Run();
32 // Mirror setting in ffmpeg_video_decoder.
33 static const int kDecodeThreads
= 2;
45 return packets_
.empty();
49 return packets_
.front();
53 AVPacket
* packet
= packets_
.front();
55 av_free_packet(packet
);
59 void push(AVPacket
* packet
) {
60 av_dup_packet(packet
);
61 packets_
.push(packet
);
71 std::queue
<AVPacket
*> packets_
;
73 DISALLOW_COPY_AND_ASSIGN(AVPacketQueue
);
76 // TODO(dalecurtis): We should really just use PipelineIntegrationTests instead
77 // of a one-off step decoder so we're exercising the real pipeline.
78 class FFmpegTest
: public testing::TestWithParam
<const char*> {
81 : av_format_context_(NULL
),
82 audio_stream_index_(-1),
83 video_stream_index_(-1),
86 decoded_audio_time_(AV_NOPTS_VALUE
),
87 decoded_audio_duration_(AV_NOPTS_VALUE
),
88 decoded_video_time_(AV_NOPTS_VALUE
),
89 decoded_video_duration_(AV_NOPTS_VALUE
),
90 duration_(AV_NOPTS_VALUE
) {
93 audio_buffer_
.reset(avcodec_alloc_frame());
94 video_buffer_
.reset(avcodec_alloc_frame());
97 virtual ~FFmpegTest() {
100 void OpenAndReadFile(const std::string
& name
) {
106 void OpenFile(const std::string
& name
) {
108 PathService::Get(base::DIR_SOURCE_ROOT
, &path
);
109 path
= path
.AppendASCII("media")
112 .AppendASCII("content")
113 .AppendASCII(name
.c_str());
114 FilePath::StringType raw_path
= path
.value();
115 EXPECT_TRUE(file_util::PathExists(path
));
118 std::string ascii_path
= WideToASCII(path
.value());
120 std::string ascii_path
= path
.value();
123 EXPECT_EQ(0, avformat_open_input(&av_format_context_
,
126 << "Could not open " << path
.value();
127 EXPECT_LE(0, avformat_find_stream_info(av_format_context_
, NULL
))
128 << "Could not find stream information for " << path
.value();
130 // Determine duration by picking max stream duration.
131 for (unsigned int i
= 0; i
< av_format_context_
->nb_streams
; ++i
) {
132 AVStream
* av_stream
= av_format_context_
->streams
[i
];
133 int64 duration
= ConvertFromTimeBase(
134 av_stream
->time_base
, av_stream
->duration
).InMicroseconds();
135 duration_
= std::max(duration_
, duration
);
138 // Final check to see if the container itself specifies a duration.
139 AVRational av_time_base
= {1, AV_TIME_BASE
};
141 ConvertFromTimeBase(av_time_base
,
142 av_format_context_
->duration
).InMicroseconds();
143 duration_
= std::max(duration_
, duration
);
147 avformat_close_input(&av_format_context_
);
151 for (unsigned int i
= 0; i
< av_format_context_
->nb_streams
; ++i
) {
152 AVStream
* av_stream
= av_format_context_
->streams
[i
];
153 AVCodecContext
* av_codec_context
= av_stream
->codec
;
154 AVCodec
* av_codec
= avcodec_find_decoder(av_codec_context
->codec_id
);
156 EXPECT_TRUE(av_codec
)
157 << "Could not find AVCodec with CodecID "
158 << av_codec_context
->codec_id
;
160 av_codec_context
->error_concealment
= FF_EC_GUESS_MVS
| FF_EC_DEBLOCK
;
161 av_codec_context
->err_recognition
= AV_EF_CAREFUL
;
162 av_codec_context
->thread_count
= kDecodeThreads
;
164 EXPECT_EQ(0, avcodec_open2(av_codec_context
, av_codec
, NULL
))
165 << "Could not open AVCodecContext with CodecID "
166 << av_codec_context
->codec_id
;
168 if (av_codec
->type
== AVMEDIA_TYPE_AUDIO
) {
169 EXPECT_EQ(-1, audio_stream_index_
) << "Found multiple audio streams.";
170 audio_stream_index_
= static_cast<int>(i
);
171 } else if (av_codec
->type
== AVMEDIA_TYPE_VIDEO
) {
172 EXPECT_EQ(-1, video_stream_index_
) << "Found multiple video streams.";
173 video_stream_index_
= static_cast<int>(i
);
175 ADD_FAILURE() << "Found unknown stream type.";
181 for (unsigned int i
= 0; i
< av_format_context_
->nb_streams
; ++i
) {
182 AVStream
* av_stream
= av_format_context_
->streams
[i
];
183 av_stream
->discard
= AVDISCARD_ALL
;
184 avcodec_close(av_stream
->codec
);
190 audio_packets_
.flush();
191 avcodec_flush_buffers(av_audio_context());
194 video_packets_
.flush();
195 avcodec_flush_buffers(av_video_context());
199 void ReadUntil(int64 time
) {
201 scoped_ptr
<AVPacket
> packet(new AVPacket());
202 if (av_read_frame(av_format_context_
, packet
.get()) < 0) {
206 int stream_index
= static_cast<int>(packet
->stream_index
);
207 int64 packet_time
= AV_NOPTS_VALUE
;
208 if (stream_index
== audio_stream_index_
) {
210 ConvertFromTimeBase(av_audio_stream()->time_base
, packet
->pts
)
212 audio_packets_
.push(packet
.release());
213 } else if (stream_index
== video_stream_index_
) {
215 ConvertFromTimeBase(av_video_stream()->time_base
, packet
->pts
)
217 video_packets_
.push(packet
.release());
219 ADD_FAILURE() << "Found packet that belongs to unknown stream.";
222 if (packet_time
> time
) {
228 void ReadRemainingFile() {
229 ReadUntil(std::numeric_limits
<int64
>::max());
232 bool StepDecodeAudio() {
233 EXPECT_TRUE(has_audio());
234 if (!has_audio() || audio_packets_
.empty()) {
238 // Decode until output is produced, end of stream, or error.
242 bool end_of_stream
= false;
245 if (audio_packets_
.empty()) {
246 av_init_packet(&packet
);
247 end_of_stream
= true;
249 memcpy(&packet
, audio_packets_
.peek(), sizeof(packet
));
252 avcodec_get_frame_defaults(audio_buffer_
.get());
253 result
= avcodec_decode_audio4(av_audio_context(), audio_buffer_
.get(),
254 &got_audio
, &packet
);
255 if (!audio_packets_
.empty()) {
256 audio_packets_
.pop();
259 EXPECT_GE(result
, 0) << "Audio decode error.";
260 if (result
< 0 || (got_audio
== 0 && end_of_stream
)) {
265 double microseconds
= 1.0L * audio_buffer_
->nb_samples
/
266 av_audio_context()->sample_rate
*
267 base::Time::kMicrosecondsPerSecond
;
268 decoded_audio_duration_
= static_cast<int64
>(microseconds
);
270 if (packet
.pts
== static_cast<int64
>(AV_NOPTS_VALUE
)) {
271 EXPECT_NE(decoded_audio_time_
, static_cast<int64
>(AV_NOPTS_VALUE
))
272 << "We never received an initial timestamped audio packet! "
273 << "Looks like there's a seeking/parsing bug in FFmpeg.";
274 decoded_audio_time_
+= decoded_audio_duration_
;
276 decoded_audio_time_
=
277 ConvertFromTimeBase(av_audio_stream()->time_base
, packet
.pts
)
286 bool StepDecodeVideo() {
287 EXPECT_TRUE(has_video());
288 if (!has_video() || video_packets_
.empty()) {
292 // Decode until output is produced, end of stream, or error.
296 bool end_of_stream
= false;
299 if (video_packets_
.empty()) {
300 av_init_packet(&packet
);
301 end_of_stream
= true;
303 memcpy(&packet
, video_packets_
.peek(), sizeof(packet
));
306 avcodec_get_frame_defaults(video_buffer_
.get());
307 av_video_context()->reordered_opaque
= packet
.pts
;
308 result
= avcodec_decode_video2(av_video_context(), video_buffer_
.get(),
309 &got_picture
, &packet
);
310 if (!video_packets_
.empty()) {
311 video_packets_
.pop();
314 EXPECT_GE(result
, 0) << "Video decode error.";
315 if (result
< 0 || (got_picture
== 0 && end_of_stream
)) {
320 AVRational doubled_time_base
;
321 doubled_time_base
.den
= av_video_stream()->r_frame_rate
.num
;
322 doubled_time_base
.num
= av_video_stream()->r_frame_rate
.den
;
323 doubled_time_base
.den
*= 2;
325 decoded_video_time_
=
326 ConvertFromTimeBase(av_video_stream()->time_base
,
327 video_buffer_
->reordered_opaque
)
329 decoded_video_duration_
=
330 ConvertFromTimeBase(doubled_time_base
,
331 2 + video_buffer_
->repeat_pict
)
338 void DecodeRemainingAudio() {
339 while (StepDecodeAudio()) {}
342 void DecodeRemainingVideo() {
343 while (StepDecodeVideo()) {}
346 void SeekTo(double position
) {
348 static_cast<int64
>(position
* base::Time::kMicrosecondsPerSecond
);
349 int flags
= AVSEEK_FLAG_BACKWARD
;
351 // Passing -1 as our stream index lets FFmpeg pick a default stream.
352 // FFmpeg will attempt to use the lowest-index video stream, if present,
353 // followed by the lowest-index audio stream.
354 EXPECT_GE(0, av_seek_frame(av_format_context_
, -1, seek_time
, flags
))
355 << "Failed to seek to position " << position
;
359 bool has_audio() { return audio_stream_index_
>= 0; }
360 bool has_video() { return video_stream_index_
>= 0; }
361 int64
decoded_audio_time() { return decoded_audio_time_
; }
362 int64
decoded_audio_duration() { return decoded_audio_duration_
; }
363 int64
decoded_video_time() { return decoded_video_time_
; }
364 int64
decoded_video_duration() { return decoded_video_duration_
; }
365 int64
duration() { return duration_
; }
367 AVStream
* av_audio_stream() {
368 return av_format_context_
->streams
[audio_stream_index_
];
370 AVStream
* av_video_stream() {
371 return av_format_context_
->streams
[video_stream_index_
];
373 AVCodecContext
* av_audio_context() {
374 return av_audio_stream()->codec
;
376 AVCodecContext
* av_video_context() {
377 return av_video_stream()->codec
;
381 void InitializeFFmpeg() {
382 static bool initialized
= false;
388 PathService::Get(base::DIR_MODULE
, &path
);
389 EXPECT_TRUE(InitializeMediaLibrary(path
))
390 << "Could not initialize media library.";
392 av_log_set_level(AV_LOG_FATAL
);
394 av_register_protocol2(&kFFmpegFileProtocol
, sizeof(kFFmpegFileProtocol
));
398 AVFormatContext
* av_format_context_
;
399 int audio_stream_index_
;
400 int video_stream_index_
;
401 AVPacketQueue audio_packets_
;
402 AVPacketQueue video_packets_
;
404 scoped_ptr_malloc
<AVFrame
, media::ScopedPtrAVFree
> audio_buffer_
;
405 scoped_ptr_malloc
<AVFrame
, media::ScopedPtrAVFree
> video_buffer_
;
407 int64 decoded_audio_time_
;
408 int64 decoded_audio_duration_
;
409 int64 decoded_video_time_
;
410 int64 decoded_video_duration_
;
413 DISALLOW_COPY_AND_ASSIGN(FFmpegTest
);
416 #define FFMPEG_TEST_CASE(name, extension) \
417 INSTANTIATE_TEST_CASE_P(name##_##extension, FFmpegTest, \
418 testing::Values(#name "." #extension));
420 // Covers all our basic formats.
421 FFMPEG_TEST_CASE(sync0
, mp4
);
422 FFMPEG_TEST_CASE(sync0
, ogv
);
423 FFMPEG_TEST_CASE(sync0
, webm
);
424 FFMPEG_TEST_CASE(sync1
, m4a
);
425 FFMPEG_TEST_CASE(sync1
, mp3
);
426 FFMPEG_TEST_CASE(sync1
, mp4
);
427 FFMPEG_TEST_CASE(sync1
, ogg
);
428 FFMPEG_TEST_CASE(sync1
, ogv
);
429 FFMPEG_TEST_CASE(sync1
, webm
);
430 FFMPEG_TEST_CASE(sync2
, m4a
);
431 FFMPEG_TEST_CASE(sync2
, mp3
);
432 FFMPEG_TEST_CASE(sync2
, mp4
);
433 FFMPEG_TEST_CASE(sync2
, ogg
);
434 FFMPEG_TEST_CASE(sync2
, ogv
);
435 FFMPEG_TEST_CASE(sync2
, webm
);
437 // Covers our LayoutTest file.
438 FFMPEG_TEST_CASE(counting
, ogv
);
440 TEST_P(FFmpegTest
, Perf
) {
442 PerfTimeLogger
timer("Opening file");
443 OpenFile(GetParam());
446 PerfTimeLogger
timer("Opening codecs");
450 PerfTimeLogger
timer("Reading file");
454 PerfTimeLogger
timer("Decoding audio");
455 DecodeRemainingAudio();
458 PerfTimeLogger
timer("Decoding video");
459 DecodeRemainingVideo();
462 PerfTimeLogger
timer("Seeking to zero");
466 PerfTimeLogger
timer("Closing codecs");
470 PerfTimeLogger
timer("Closing file");
475 TEST_P(FFmpegTest
, Loop_Audio
) {
476 OpenAndReadFile(GetParam());
481 const int kSteps
= 4;
482 std::vector
<int64
> expected_timestamps_
;
483 for (int i
= 0; i
< kSteps
; ++i
) {
484 EXPECT_TRUE(StepDecodeAudio());
485 expected_timestamps_
.push_back(decoded_audio_time());
491 for (int i
= 0; i
< kSteps
; ++i
) {
492 EXPECT_TRUE(StepDecodeAudio());
493 EXPECT_EQ(expected_timestamps_
[i
], decoded_audio_time())
494 << "Frame " << i
<< " had a mismatched timestamp.";
501 TEST_P(FFmpegTest
, Loop_Video
) {
502 OpenAndReadFile(GetParam());
507 const int kSteps
= 4;
508 std::vector
<int64
> expected_timestamps_
;
509 for (int i
= 0; i
< kSteps
; ++i
) {
510 EXPECT_TRUE(StepDecodeVideo());
511 expected_timestamps_
.push_back(decoded_video_time());
517 for (int i
= 0; i
< kSteps
; ++i
) {
518 EXPECT_TRUE(StepDecodeVideo());
519 EXPECT_EQ(expected_timestamps_
[i
], decoded_video_time())
520 << "Frame " << i
<< " had a mismatched timestamp.";
527 TEST_P(FFmpegTest
, Seek_Audio
) {
528 OpenAndReadFile(GetParam());
529 if (!has_audio() && duration() >= 0.5) {
533 SeekTo(duration() - 0.5);
536 EXPECT_TRUE(StepDecodeAudio());
537 EXPECT_NE(static_cast<int64
>(AV_NOPTS_VALUE
), decoded_audio_time());
543 TEST_P(FFmpegTest
, Seek_Video
) {
544 OpenAndReadFile(GetParam());
545 if (!has_video() && duration() >= 0.5) {
549 SeekTo(duration() - 0.5);
552 EXPECT_TRUE(StepDecodeVideo());
553 EXPECT_NE(static_cast<int64
>(AV_NOPTS_VALUE
), decoded_video_time());
559 TEST_P(FFmpegTest
, Decode_Audio
) {
560 OpenAndReadFile(GetParam());
565 int64 last_audio_time
= AV_NOPTS_VALUE
;
566 while (StepDecodeAudio()) {
567 ASSERT_GT(decoded_audio_time(), last_audio_time
);
568 last_audio_time
= decoded_audio_time();
575 TEST_P(FFmpegTest
, Decode_Video
) {
576 OpenAndReadFile(GetParam());
581 int64 last_video_time
= AV_NOPTS_VALUE
;
582 while (StepDecodeVideo()) {
583 ASSERT_GT(decoded_video_time(), last_video_time
);
584 last_video_time
= decoded_video_time();
591 TEST_P(FFmpegTest
, Duration
) {
592 OpenAndReadFile(GetParam());
595 DecodeRemainingAudio();
599 DecodeRemainingVideo();
602 double expected
= static_cast<double>(duration());
603 double actual
= static_cast<double>(
604 std::max(decoded_audio_time() + decoded_audio_duration(),
605 decoded_video_time() + decoded_video_duration()));
606 EXPECT_NEAR(expected
, actual
, 500000)
607 << "Duration is off by more than 0.5 seconds.";
613 TEST_F(FFmpegTest
, VideoPlayedCollapse
) {
614 OpenFile("test.ogv");
619 EXPECT_TRUE(StepDecodeVideo());
620 VLOG(1) << decoded_video_time();
624 EXPECT_TRUE(StepDecodeVideo());
625 VLOG(1) << decoded_video_time();
629 EXPECT_TRUE(StepDecodeVideo());
630 VLOG(1) << decoded_video_time();