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 // Software qualification test for FFmpeg. This test is used to certify that
6 // software decoding quality and performance of FFmpeg meets a mimimum
13 #include "base/at_exit.h"
14 #include "base/basictypes.h"
15 #include "base/command_line.h"
16 #include "base/file_util.h"
17 #include "base/files/file_path.h"
18 #include "base/files/memory_mapped_file.h"
19 #include "base/logging.h"
21 #include "base/path_service.h"
22 #include "base/string_util.h"
23 #include "base/time.h"
24 #include "base/utf_string_conversions.h"
25 #include "media/base/djb2.h"
26 #include "media/base/media.h"
27 #include "media/ffmpeg/ffmpeg_common.h"
28 #include "media/filters/ffmpeg_glue.h"
29 #include "media/filters/ffmpeg_video_decoder.h"
30 #include "media/filters/in_memory_url_protocol.h"
33 #define SHOW_VERBOSE 1
35 #define SHOW_VERBOSE 0
40 // Enable to build with exception handler
41 //#define ENABLE_WINDOWS_EXCEPTIONS 1
43 #ifdef ENABLE_WINDOWS_EXCEPTIONS
44 // warning: disable warning about exception handler.
45 #pragma warning(disable:4509)
48 // Thread priorities to make benchmark more stable.
50 void EnterTimingSection() {
51 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL
);
54 void LeaveTimingSection() {
55 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL
);
58 void EnterTimingSection() {
60 struct sched_param param
;
62 pthread_attr_init(&pta
);
63 memset(¶m
, 0, sizeof(param
));
64 param
.sched_priority
= 78;
65 pthread_attr_setschedparam(&pta
, ¶m
);
66 pthread_attr_destroy(&pta
);
69 void LeaveTimingSection() {
73 int main(int argc
, const char** argv
) {
74 base::AtExitManager exit_manager
;
76 CommandLine::Init(argc
, argv
);
77 const CommandLine
* cmd_line
= CommandLine::ForCurrentProcess();
79 const CommandLine::StringVector
& filenames
= cmd_line
->GetArgs();
81 if (filenames
.empty()) {
82 std::cerr
<< "Usage: " << argv
[0] << " MEDIAFILE" << std::endl
;
86 // Initialize our media library (try loading DLLs, etc.) before continuing.
87 base::FilePath media_path
;
88 PathService::Get(base::DIR_MODULE
, &media_path
);
89 if (!media::InitializeMediaLibrary(media_path
)) {
90 std::cerr
<< "Unable to initialize the media library.";
94 // Retrieve command line options.
95 base::FilePath
in_path(filenames
[0]);
96 base::FilePath out_path
;
97 if (filenames
.size() > 1)
98 out_path
= base::FilePath(filenames
[1]);
100 // Default flags that match Chrome defaults.
101 int video_threads
= 2;
106 unsigned int hash_value
= 5381u; // Seed for DJB2.
107 bool hash_djb2
= false;
109 base::MD5Context ctx
; // Intermediate MD5 data: do not use
111 bool hash_md5
= false;
113 std::ostream
* log_out
= &std::cout
;
114 #if defined(ENABLE_WINDOWS_EXCEPTIONS)
115 // Catch exceptions so this tool can be used in automated testing.
119 base::MemoryMappedFile file_data
;
120 file_data
.Initialize(in_path
);
121 media::InMemoryUrlProtocol
protocol(
122 file_data
.data(), file_data
.length(), false);
124 // Register FFmpeg and attempt to open file.
125 media::FFmpegGlue
glue(&protocol
);
126 if (!glue
.OpenContext()) {
127 std::cerr
<< "Error: Could not open input for "
128 << in_path
.value() << std::endl
;
132 AVFormatContext
* format_context
= glue
.format_context();
136 if (!out_path
.empty()) {
137 output
= file_util::OpenFile(out_path
, "wb");
139 std::cerr
<< "Error: Could not open output "
140 << out_path
.value() << std::endl
;
145 // Parse a little bit of the stream to fill out the format context.
146 if (avformat_find_stream_info(format_context
, NULL
) < 0) {
147 std::cerr
<< "Error: Could not find stream info for "
148 << in_path
.value() << std::endl
;
152 // Find our target stream(s)
153 int video_stream
= -1;
154 int audio_stream
= -1;
155 for (size_t i
= 0; i
< format_context
->nb_streams
; ++i
) {
156 AVCodecContext
* codec_context
= format_context
->streams
[i
]->codec
;
158 if (codec_context
->codec_type
== AVMEDIA_TYPE_VIDEO
&& video_stream
< 0) {
164 if (codec_context
->codec_type
== AVMEDIA_TYPE_AUDIO
&& audio_stream
< 0) {
177 AVCodec
* codec
= avcodec_find_decoder(codec_context
->codec_id
);
178 if (!codec
|| (codec_context
->codec_type
== AVMEDIA_TYPE_UNKNOWN
)) {
179 *log_out
<< "Stream #" << i
<< ": Unknown" << std::endl
;
181 // Print out stream information
182 *log_out
<< "Stream #" << i
<< ": " << codec
->name
<< " ("
183 << codec
->long_name
<< ")" << std::endl
;
187 int target_stream
= video_stream
;
188 AVMediaType target_codec
= AVMEDIA_TYPE_VIDEO
;
189 if (target_stream
< 0) {
190 target_stream
= audio_stream
;
191 target_codec
= AVMEDIA_TYPE_AUDIO
;
194 // Only continue if we found our target stream.
195 if (target_stream
< 0) {
196 std::cerr
<< "Error: Could not find target stream "
197 << target_stream
<< " for " << in_path
.value() << std::endl
;
201 // Prepare FFmpeg structures.
203 AVCodecContext
* codec_context
= format_context
->streams
[target_stream
]->codec
;
204 AVCodec
* codec
= avcodec_find_decoder(codec_context
->codec_id
);
206 // Only continue if we found our codec.
208 std::cerr
<< "Error: Could not find codec for "
209 << in_path
.value() << std::endl
;
213 codec_context
->error_concealment
= FF_EC_GUESS_MVS
| FF_EC_DEBLOCK
;
215 // Initialize threaded decode.
216 if (target_codec
== AVMEDIA_TYPE_VIDEO
&& video_threads
> 0) {
217 codec_context
->thread_count
= video_threads
;
220 // Initialize our codec.
221 if (avcodec_open2(codec_context
, codec
, NULL
) < 0) {
222 std::cerr
<< "Error: Could not open codec "
223 << codec_context
->codec
->name
<< " for "
224 << in_path
.value() << std::endl
;
228 // Buffer used for audio decoding.
229 scoped_ptr_malloc
<AVFrame
, media::ScopedPtrAVFree
> audio_frame(
230 avcodec_alloc_frame());
232 std::cerr
<< "Error: avcodec_alloc_frame for "
233 << in_path
.value() << std::endl
;
237 // Buffer used for video decoding.
238 scoped_ptr_malloc
<AVFrame
, media::ScopedPtrAVFree
> video_frame(
239 avcodec_alloc_frame());
241 std::cerr
<< "Error: avcodec_alloc_frame for "
242 << in_path
.value() << std::endl
;
247 EnterTimingSection();
248 std::vector
<double> decode_times
;
249 decode_times
.reserve(4096);
250 // Parse through the entire stream until we hit EOF.
252 base::TimeTicks start
= base::TimeTicks::HighResNow();
257 read_result
= av_read_frame(format_context
, &packet
);
259 if (read_result
< 0) {
264 av_seek_frame(format_context
, -1, 0, AVSEEK_FLAG_BACKWARD
);
269 packet
.stream_index
= target_stream
;
276 // Only decode packets from our target stream.
277 if (packet
.stream_index
== target_stream
) {
279 if (target_codec
== AVMEDIA_TYPE_AUDIO
) {
283 avcodec_get_frame_defaults(audio_frame
.get());
285 base::TimeTicks decode_start
= base::TimeTicks::HighResNow();
286 result
= avcodec_decode_audio4(codec_context
, audio_frame
.get(),
287 &got_audio
, &packet
);
288 base::TimeDelta delta
= base::TimeTicks::HighResNow() - decode_start
;
291 size_out
= av_samples_get_buffer_size(
292 NULL
, codec_context
->channels
, audio_frame
->nb_samples
,
293 codec_context
->sample_fmt
, 1);
296 if (got_audio
&& size_out
) {
297 decode_times
.push_back(delta
.InMillisecondsF());
299 read_result
= 0; // Force continuation.
302 if (fwrite(audio_frame
->data
[0], 1, size_out
, output
) !=
303 static_cast<size_t>(size_out
)) {
304 std::cerr
<< "Error: Could not write "
305 << size_out
<< " bytes for " << in_path
.value()
311 const uint8
* u8_samples
=
312 reinterpret_cast<const uint8
*>(audio_frame
->data
[0]);
314 hash_value
= DJB2Hash(u8_samples
, size_out
, hash_value
);
319 base::StringPiece(reinterpret_cast<const char*>(u8_samples
),
323 } else if (target_codec
== AVMEDIA_TYPE_VIDEO
) {
326 avcodec_get_frame_defaults(video_frame
.get());
328 base::TimeTicks decode_start
= base::TimeTicks::HighResNow();
329 result
= avcodec_decode_video2(codec_context
, video_frame
.get(),
330 &got_picture
, &packet
);
331 base::TimeDelta delta
= base::TimeTicks::HighResNow() - decode_start
;
334 decode_times
.push_back(delta
.InMillisecondsF());
336 read_result
= 0; // Force continuation.
338 for (int plane
= 0; plane
< 3; ++plane
) {
339 const uint8
* source
= video_frame
->data
[plane
];
340 const size_t source_stride
= video_frame
->linesize
[plane
];
341 size_t bytes_per_line
= codec_context
->width
;
342 size_t copy_lines
= codec_context
->height
;
344 switch (codec_context
->pix_fmt
) {
345 case PIX_FMT_YUV420P
:
346 case PIX_FMT_YUVJ420P
:
348 copy_lines
= (copy_lines
+ 1) / 2;
350 case PIX_FMT_YUV422P
:
351 case PIX_FMT_YUVJ422P
:
354 case PIX_FMT_YUV444P
:
355 case PIX_FMT_YUVJ444P
:
358 std::cerr
<< "Error: Unknown video format "
359 << codec_context
->pix_fmt
;
364 for (size_t i
= 0; i
< copy_lines
; ++i
) {
365 if (fwrite(source
, 1, bytes_per_line
, output
) !=
367 std::cerr
<< "Error: Could not write data after "
368 << copy_lines
<< " lines for "
369 << in_path
.value() << std::endl
;
372 source
+= source_stride
;
376 for (size_t i
= 0; i
< copy_lines
; ++i
) {
377 hash_value
= DJB2Hash(source
, bytes_per_line
, hash_value
);
378 source
+= source_stride
;
382 for (size_t i
= 0; i
< copy_lines
; ++i
) {
385 base::StringPiece(reinterpret_cast<const char*>(source
),
387 source
+= source_stride
;
396 // Make sure our decoding went OK.
398 std::cerr
<< "Error: avcodec_decode returned "
399 << result
<< " for " << in_path
.value() << std::endl
;
404 av_free_packet(&packet
);
406 if (max_frames
&& (frames
>= max_frames
))
408 } while (read_result
>= 0);
410 base::TimeDelta total
= base::TimeTicks::HighResNow() - start
;
412 LeaveTimingSection();
416 file_util::CloseFile(output
);
418 // Calculate the sum of times. Note that some of these may be zero.
420 for (size_t i
= 0; i
< decode_times
.size(); ++i
) {
421 sum
+= decode_times
[i
];
425 if (target_codec
== AVMEDIA_TYPE_AUDIO
) {
426 // Calculate the average milliseconds per frame.
427 // Audio decoding is usually in the millisecond or range, and
428 // best expressed in time (ms) rather than FPS, which can approach
430 double ms
= sum
/ frames
;
431 // Print our results.
432 log_out
->setf(std::ios::fixed
);
433 log_out
->precision(2);
434 *log_out
<< "TIME PER FRAME (MS):" << std::setw(11) << ms
<< std::endl
;
435 } else if (target_codec
== AVMEDIA_TYPE_VIDEO
) {
436 // Calculate the average frames per second.
437 // Video decoding is expressed in Frames Per Second - a term easily
438 // understood and should exceed a typical target of 30 fps.
439 double fps
= frames
* 1000.0 / sum
;
440 // Print our results.
441 log_out
->setf(std::ios::fixed
);
442 log_out
->precision(2);
443 *log_out
<< "FPS:" << std::setw(11) << fps
<< std::endl
;
448 // Print our results.
449 log_out
->setf(std::ios::fixed
);
450 log_out
->precision(2);
451 *log_out
<< std::endl
;
452 *log_out
<< " Frames:" << std::setw(11) << frames
454 *log_out
<< " Total:" << std::setw(11) << total
.InMillisecondsF()
455 << " ms" << std::endl
;
456 *log_out
<< " Summation:" << std::setw(11) << sum
457 << " ms" << std::endl
;
460 // Calculate the average time per frame.
461 double average
= sum
/ frames
;
463 // Calculate the sum of the squared differences.
464 // Standard deviation will only be accurate if no threads are used.
465 // TODO(fbarchard): Rethink standard deviation calculation.
466 double squared_sum
= 0;
467 for (int i
= 0; i
< frames
; ++i
) {
468 double difference
= decode_times
[i
] - average
;
469 squared_sum
+= difference
* difference
;
472 // Calculate the standard deviation (jitter).
473 double stddev
= sqrt(squared_sum
/ frames
);
475 *log_out
<< " Average:" << std::setw(11) << average
476 << " ms" << std::endl
;
477 *log_out
<< " StdDev:" << std::setw(11) << stddev
478 << " ms" << std::endl
;
481 *log_out
<< " DJB2 Hash:" << std::setw(11) << hash_value
482 << " " << in_path
.value() << std::endl
;
485 base::MD5Digest digest
; // The result of the computation.
486 base::MD5Final(&digest
, &ctx
);
487 *log_out
<< " MD5 Hash: " << base::MD5DigestToBase16(digest
)
488 << " " << in_path
.value() << std::endl
;
490 #endif // SHOW_VERBOSE
491 #if defined(ENABLE_WINDOWS_EXCEPTIONS)
492 } __except(EXCEPTION_EXECUTE_HANDLER
) {
493 *log_out
<< " Exception:" << std::setw(11) << GetExceptionCode()
494 << " " << in_path
.value() << std::endl
;
498 CommandLine::Reset();