Re-enable index-basics-workers test to see if still times
[chromium-blink-merge.git] / media / test / ffmpeg_tests / ffmpeg_tests.cc
blob222c9874b2723a94a84ea9843cefab1f424d5fed
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
7 // standard.
9 #include <iomanip>
10 #include <iostream>
11 #include <string>
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"
20 #include "base/md5.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"
32 #ifdef DEBUG
33 #define SHOW_VERBOSE 1
34 #else
35 #define SHOW_VERBOSE 0
36 #endif
38 #if defined(OS_WIN)
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)
46 #endif
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);
57 #else
58 void EnterTimingSection() {
59 pthread_attr_t pta;
60 struct sched_param param;
62 pthread_attr_init(&pta);
63 memset(&param, 0, sizeof(param));
64 param.sched_priority = 78;
65 pthread_attr_setschedparam(&pta, &param);
66 pthread_attr_destroy(&pta);
69 void LeaveTimingSection() {
71 #endif
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;
83 return 1;
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.";
91 return 1;
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;
102 int max_frames = 0;
103 int max_loops = 0;
104 bool flush = false;
106 unsigned int hash_value = 5381u; // Seed for DJB2.
107 bool hash_djb2 = false;
109 base::MD5Context ctx; // Intermediate MD5 data: do not use
110 base::MD5Init(&ctx);
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.
116 __try {
117 #endif
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;
129 return 1;
132 AVFormatContext* format_context = glue.format_context();
134 // Open output file.
135 FILE *output = NULL;
136 if (!out_path.empty()) {
137 output = file_util::OpenFile(out_path, "wb");
138 if (!output) {
139 std::cerr << "Error: Could not open output "
140 << out_path.value() << std::endl;
141 return 1;
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;
149 return 1;
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) {
159 #if SHOW_VERBOSE
160 *log_out << "V ";
161 #endif
162 video_stream = i;
163 } else {
164 if (codec_context->codec_type == AVMEDIA_TYPE_AUDIO && audio_stream < 0) {
165 #if SHOW_VERBOSE
166 *log_out << "A ";
167 #endif
168 audio_stream = i;
169 } else {
170 #if SHOW_VERBOSE
171 *log_out << " ";
172 #endif
176 #if SHOW_VERBOSE
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;
180 } else {
181 // Print out stream information
182 *log_out << "Stream #" << i << ": " << codec->name << " ("
183 << codec->long_name << ")" << std::endl;
185 #endif
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;
198 return 1;
201 // Prepare FFmpeg structures.
202 AVPacket packet;
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.
207 if (!codec) {
208 std::cerr << "Error: Could not find codec for "
209 << in_path.value() << std::endl;
210 return 1;
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;
225 return 1;
228 // Buffer used for audio decoding.
229 scoped_ptr_malloc<AVFrame, media::ScopedPtrAVFree> audio_frame(
230 avcodec_alloc_frame());
231 if (!audio_frame) {
232 std::cerr << "Error: avcodec_alloc_frame for "
233 << in_path.value() << std::endl;
234 return 1;
237 // Buffer used for video decoding.
238 scoped_ptr_malloc<AVFrame, media::ScopedPtrAVFree> video_frame(
239 avcodec_alloc_frame());
240 if (!video_frame) {
241 std::cerr << "Error: avcodec_alloc_frame for "
242 << in_path.value() << std::endl;
243 return 1;
246 // Stats collector.
247 EnterTimingSection();
248 std::vector<double> decode_times;
249 decode_times.reserve(4096);
250 // Parse through the entire stream until we hit EOF.
251 #if SHOW_VERBOSE
252 base::TimeTicks start = base::TimeTicks::HighResNow();
253 #endif
254 int frames = 0;
255 int read_result = 0;
256 do {
257 read_result = av_read_frame(format_context, &packet);
259 if (read_result < 0) {
260 if (max_loops) {
261 --max_loops;
263 if (max_loops > 0) {
264 av_seek_frame(format_context, -1, 0, AVSEEK_FLAG_BACKWARD);
265 read_result = 0;
266 continue;
268 if (flush) {
269 packet.stream_index = target_stream;
270 packet.size = 0;
271 } else {
272 break;
276 // Only decode packets from our target stream.
277 if (packet.stream_index == target_stream) {
278 int result = -1;
279 if (target_codec == AVMEDIA_TYPE_AUDIO) {
280 int size_out = 0;
281 int got_audio = 0;
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;
290 if (got_audio) {
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());
298 ++frames;
299 read_result = 0; // Force continuation.
301 if (output) {
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()
306 << std::endl;
307 return 1;
311 const uint8* u8_samples =
312 reinterpret_cast<const uint8*>(audio_frame->data[0]);
313 if (hash_djb2) {
314 hash_value = DJB2Hash(u8_samples, size_out, hash_value);
316 if (hash_md5) {
317 base::MD5Update(
318 &ctx,
319 base::StringPiece(reinterpret_cast<const char*>(u8_samples),
320 size_out));
323 } else if (target_codec == AVMEDIA_TYPE_VIDEO) {
324 int got_picture = 0;
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;
333 if (got_picture) {
334 decode_times.push_back(delta.InMillisecondsF());
335 ++frames;
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;
343 if (plane != 0) {
344 switch (codec_context->pix_fmt) {
345 case PIX_FMT_YUV420P:
346 case PIX_FMT_YUVJ420P:
347 bytes_per_line /= 2;
348 copy_lines = (copy_lines + 1) / 2;
349 break;
350 case PIX_FMT_YUV422P:
351 case PIX_FMT_YUVJ422P:
352 bytes_per_line /= 2;
353 break;
354 case PIX_FMT_YUV444P:
355 case PIX_FMT_YUVJ444P:
356 break;
357 default:
358 std::cerr << "Error: Unknown video format "
359 << codec_context->pix_fmt;
360 return 1;
363 if (output) {
364 for (size_t i = 0; i < copy_lines; ++i) {
365 if (fwrite(source, 1, bytes_per_line, output) !=
366 bytes_per_line) {
367 std::cerr << "Error: Could not write data after "
368 << copy_lines << " lines for "
369 << in_path.value() << std::endl;
370 return 1;
372 source += source_stride;
375 if (hash_djb2) {
376 for (size_t i = 0; i < copy_lines; ++i) {
377 hash_value = DJB2Hash(source, bytes_per_line, hash_value);
378 source += source_stride;
381 if (hash_md5) {
382 for (size_t i = 0; i < copy_lines; ++i) {
383 base::MD5Update(
384 &ctx,
385 base::StringPiece(reinterpret_cast<const char*>(source),
386 bytes_per_line));
387 source += source_stride;
392 } else {
393 NOTREACHED();
396 // Make sure our decoding went OK.
397 if (result < 0) {
398 std::cerr << "Error: avcodec_decode returned "
399 << result << " for " << in_path.value() << std::endl;
400 return 1;
403 // Free our packet.
404 av_free_packet(&packet);
406 if (max_frames && (frames >= max_frames))
407 break;
408 } while (read_result >= 0);
409 #if SHOW_VERBOSE
410 base::TimeDelta total = base::TimeTicks::HighResNow() - start;
411 #endif
412 LeaveTimingSection();
414 // Clean up.
415 if (output)
416 file_util::CloseFile(output);
418 // Calculate the sum of times. Note that some of these may be zero.
419 double sum = 0;
420 for (size_t i = 0; i < decode_times.size(); ++i) {
421 sum += decode_times[i];
424 if (sum > 0) {
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
429 // infinity.
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;
447 #if SHOW_VERBOSE
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
453 << std::endl;
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;
459 if (frames > 0) {
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;
480 if (hash_djb2) {
481 *log_out << " DJB2 Hash:" << std::setw(11) << hash_value
482 << " " << in_path.value() << std::endl;
484 if (hash_md5) {
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;
495 return 1;
497 #endif
498 CommandLine::Reset();
499 return 0;