Home | History | Annotate | Download | only in media_bench
      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 // Standalone benchmarking application based on FFmpeg.  This tool is used to
      6 // measure decoding performance between different FFmpeg compile and run-time
      7 // options.  We also use this tool to measure performance regressions when
      8 // testing newer builds of FFmpeg from trunk.
      9 
     10 #include <iomanip>
     11 #include <iostream>
     12 #include <string>
     13 
     14 #include "base/at_exit.h"
     15 #include "base/basictypes.h"
     16 #include "base/command_line.h"
     17 #include "base/file_util.h"
     18 #include "base/files/file_path.h"
     19 #include "base/files/memory_mapped_file.h"
     20 #include "base/logging.h"
     21 #include "base/md5.h"
     22 #include "base/path_service.h"
     23 #include "base/strings/string_number_conversions.h"
     24 #include "base/strings/string_util.h"
     25 #include "base/strings/utf_string_conversions.h"
     26 #include "base/time/time.h"
     27 #include "build/build_config.h"
     28 #include "media/base/djb2.h"
     29 #include "media/base/media.h"
     30 #include "media/ffmpeg/ffmpeg_common.h"
     31 #include "media/filters/ffmpeg_glue.h"
     32 #include "media/filters/ffmpeg_video_decoder.h"
     33 #include "media/filters/in_memory_url_protocol.h"
     34 
     35 // For pipe _setmode to binary
     36 #if defined(OS_WIN)
     37 #include <fcntl.h>
     38 #include <io.h>
     39 #endif
     40 
     41 namespace switches {
     42 const char kStream[]       = "stream";
     43 const char kVideoThreads[] = "video-threads";
     44 const char kFast2[]        = "fast2";
     45 const char kErrorCorrection[] = "error-correction";
     46 const char kSkip[]         = "skip";
     47 const char kFlush[]        = "flush";
     48 const char kDjb2[]         = "djb2";
     49 const char kMd5[]          = "md5";
     50 const char kFrames[]       = "frames";
     51 const char kLoop[]         = "loop";
     52 
     53 }  // namespace switches
     54 
     55 #if defined(OS_WIN)
     56 
     57 // Enable to build with exception handler
     58 // #define ENABLE_WINDOWS_EXCEPTIONS 1
     59 
     60 #ifdef ENABLE_WINDOWS_EXCEPTIONS
     61 // warning: disable warning about exception handler.
     62 #pragma warning(disable:4509)
     63 #endif
     64 
     65 // Thread priorities to make benchmark more stable.
     66 
     67 void EnterTimingSection() {
     68   SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
     69 }
     70 
     71 void LeaveTimingSection() {
     72   SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
     73 }
     74 #else
     75 void EnterTimingSection() {
     76   pthread_attr_t pta;
     77   struct sched_param param;
     78 
     79   pthread_attr_init(&pta);
     80   memset(&param, 0, sizeof(param));
     81   param.sched_priority = 78;
     82   pthread_attr_setschedparam(&pta, &param);
     83   pthread_attr_destroy(&pta);
     84 }
     85 
     86 void LeaveTimingSection() {
     87 }
     88 #endif
     89 
     90 int main(int argc, const char** argv) {
     91   base::AtExitManager exit_manager;
     92 
     93   CommandLine::Init(argc, argv);
     94 
     95   logging::LoggingSettings settings;
     96   settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
     97   logging::InitLogging(settings);
     98 
     99   const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
    100   const CommandLine::StringVector& filenames = cmd_line->GetArgs();
    101   if (filenames.empty()) {
    102     std::cerr << "Usage: " << argv[0] << " [OPTIONS] FILE [DUMPFILE]\n"
    103               << "  --stream=[audio|video]          "
    104               << "Benchmark either the audio or video stream\n"
    105               << "  --video-threads=N               "
    106               << "Decode video using N threads\n"
    107               << "  --frames=N                      "
    108               << "Decode N frames\n"
    109               << "  --loop=N                        "
    110               << "Loop N times\n"
    111               << "  --fast2                         "
    112               << "Enable fast2 flag\n"
    113               << "  --error-correction              "
    114               << "Enable ffmpeg error correction\n"
    115               << "  --flush                         "
    116               << "Flush last frame\n"
    117               << "  --djb2 (aka --hash)             "
    118               << "Hash decoded buffers (DJB2)\n"
    119               << "  --md5                           "
    120               << "Hash decoded buffers (MD5)\n"
    121               << "  --skip=[1|2|3]                  "
    122               << "1=loop nonref, 2=loop, 3= frame nonref\n" << std::endl;
    123     return 1;
    124   }
    125 
    126   // Initialize our media library (try loading DLLs, etc.) before continuing.
    127   base::FilePath media_path;
    128   PathService::Get(base::DIR_MODULE, &media_path);
    129   if (!media::InitializeMediaLibrary(media_path)) {
    130     std::cerr << "Unable to initialize the media library." << std::endl;
    131     return 1;
    132   }
    133 
    134   // Retrieve command line options.
    135   base::FilePath in_path(filenames[0]);
    136   base::FilePath out_path;
    137   if (filenames.size() > 1)
    138     out_path = base::FilePath(filenames[1]);
    139   AVMediaType target_codec = AVMEDIA_TYPE_UNKNOWN;
    140 
    141   // Determine whether to benchmark audio or video decoding.
    142   std::string stream(cmd_line->GetSwitchValueASCII(switches::kStream));
    143   if (!stream.empty()) {
    144     if (stream.compare("audio") == 0) {
    145       target_codec = AVMEDIA_TYPE_AUDIO;
    146     } else if (stream.compare("video") == 0) {
    147       target_codec = AVMEDIA_TYPE_VIDEO;
    148     } else {
    149       std::cerr << "Unknown --stream option " << stream << std::endl;
    150       return 1;
    151     }
    152   }
    153 
    154   // Determine number of threads to use for video decoding (optional).
    155   int video_threads = 0;
    156   std::string threads(cmd_line->GetSwitchValueASCII(switches::kVideoThreads));
    157   if (!threads.empty() &&
    158       !base::StringToInt(threads, &video_threads)) {
    159     video_threads = 0;
    160   }
    161 
    162   // Determine number of frames to decode (optional).
    163   int max_frames = 0;
    164   std::string frames_opt(cmd_line->GetSwitchValueASCII(switches::kFrames));
    165   if (!frames_opt.empty() &&
    166       !base::StringToInt(frames_opt, &max_frames)) {
    167     max_frames = 0;
    168   }
    169 
    170   // Determine number of times to loop (optional).
    171   int max_loops = 0;
    172   std::string loop_opt(cmd_line->GetSwitchValueASCII(switches::kLoop));
    173   if (!loop_opt.empty() &&
    174       !base::StringToInt(loop_opt, &max_loops)) {
    175     max_loops = 0;
    176   }
    177 
    178   bool fast2 = false;
    179   if (cmd_line->HasSwitch(switches::kFast2)) {
    180     fast2 = true;
    181   }
    182 
    183   bool error_correction = false;
    184   if (cmd_line->HasSwitch(switches::kErrorCorrection)) {
    185     error_correction = true;
    186   }
    187 
    188   bool flush = false;
    189   if (cmd_line->HasSwitch(switches::kFlush)) {
    190     flush = true;
    191   }
    192 
    193   unsigned int hash_value = 5381u;  // Seed for DJB2.
    194   bool hash_djb2 = false;
    195   if (cmd_line->HasSwitch(switches::kDjb2)) {
    196     hash_djb2 = true;
    197   }
    198 
    199   base::MD5Context ctx;  // Intermediate MD5 data: do not use
    200   base::MD5Init(&ctx);
    201   bool hash_md5 = false;
    202   if (cmd_line->HasSwitch(switches::kMd5))
    203     hash_md5 = true;
    204 
    205   int skip = 0;
    206   if (cmd_line->HasSwitch(switches::kSkip)) {
    207     std::string skip_opt(cmd_line->GetSwitchValueASCII(switches::kSkip));
    208     if (!base::StringToInt(skip_opt, &skip)) {
    209       skip = 0;
    210     }
    211   }
    212 
    213   std::ostream* log_out = &std::cout;
    214 #if defined(ENABLE_WINDOWS_EXCEPTIONS)
    215   // Catch exceptions so this tool can be used in automated testing.
    216   __try {
    217 #endif
    218 
    219   base::MemoryMappedFile file_data;
    220   file_data.Initialize(in_path);
    221   media::InMemoryUrlProtocol protocol(
    222       file_data.data(), file_data.length(), false);
    223 
    224   // Register FFmpeg and attempt to open file.
    225   media::FFmpegGlue glue(&protocol);
    226   if (!glue.OpenContext()) {
    227     std::cerr << "Error: Could not open input for "
    228               << in_path.value() << std::endl;
    229     return 1;
    230   }
    231 
    232   AVFormatContext* format_context = glue.format_context();
    233 
    234   // Open output file.
    235   FILE *output = NULL;
    236   if (!out_path.empty()) {
    237     // TODO(fbarchard): Add pipe:1 for piping to stderr.
    238     if (out_path.value().substr(0, 5) == FILE_PATH_LITERAL("pipe:") ||
    239         out_path.value() == FILE_PATH_LITERAL("-")) {
    240       output = stdout;
    241       log_out = &std::cerr;
    242 #if defined(OS_WIN)
    243       _setmode(_fileno(stdout), _O_BINARY);
    244 #endif
    245     } else {
    246       output = file_util::OpenFile(out_path, "wb");
    247     }
    248     if (!output) {
    249       std::cerr << "Error: Could not open output "
    250                 << out_path.value() << std::endl;
    251       return 1;
    252     }
    253   }
    254 
    255   // Parse a little bit of the stream to fill out the format context.
    256   if (avformat_find_stream_info(format_context, NULL) < 0) {
    257     std::cerr << "Error: Could not find stream info for "
    258               << in_path.value() << std::endl;
    259     return 1;
    260   }
    261 
    262   // Find our target stream.
    263   int target_stream = -1;
    264   for (size_t i = 0; i < format_context->nb_streams; ++i) {
    265     AVCodecContext* codec_context = format_context->streams[i]->codec;
    266     AVCodec* codec = avcodec_find_decoder(codec_context->codec_id);
    267 
    268     // See if we found our target codec.
    269     if (codec_context->codec_type == target_codec && target_stream < 0) {
    270       *log_out << "* ";
    271       target_stream = i;
    272     } else {
    273       *log_out << "  ";
    274     }
    275 
    276     if (!codec || (codec_context->codec_type == AVMEDIA_TYPE_UNKNOWN)) {
    277       *log_out << "Stream #" << i << ": Unknown" << std::endl;
    278     } else {
    279       // Print out stream information
    280       *log_out << "Stream #" << i << ": " << codec->name << " ("
    281                << codec->long_name << ")" << std::endl;
    282     }
    283   }
    284 
    285   // Only continue if we found our target stream.
    286   if (target_stream < 0) {
    287     std::cerr << "Error: Could not find target stream "
    288               << target_stream << " for " << in_path.value() << std::endl;
    289     return 1;
    290   }
    291 
    292   // Prepare FFmpeg structures.
    293   AVPacket packet;
    294   AVCodecContext* codec_context = format_context->streams[target_stream]->codec;
    295   AVCodec* codec = avcodec_find_decoder(codec_context->codec_id);
    296 
    297   // Only continue if we found our codec.
    298   if (!codec) {
    299     std::cerr << "Error: Could not find codec for "
    300               << in_path.value() << std::endl;
    301     return 1;
    302   }
    303 
    304   if (skip == 1) {
    305     codec_context->skip_loop_filter = AVDISCARD_NONREF;
    306   } else if (skip == 2) {
    307     codec_context->skip_loop_filter = AVDISCARD_ALL;
    308   } else if (skip == 3) {
    309     codec_context->skip_loop_filter = AVDISCARD_ALL;
    310     codec_context->skip_frame = AVDISCARD_NONREF;
    311   }
    312   if (fast2) {
    313     // Note this flag is no longer necessary for H264 multithreading.
    314     codec_context->flags2 |= CODEC_FLAG2_FAST;
    315   }
    316   if (error_correction) {
    317     codec_context->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
    318   }
    319 
    320   // Initialize threaded decode.
    321   if (target_codec == AVMEDIA_TYPE_VIDEO && video_threads > 0) {
    322     codec_context->thread_count = video_threads;
    323   }
    324 
    325   // Initialize our codec.
    326   if (avcodec_open2(codec_context, codec, NULL) < 0) {
    327     std::cerr << "Error: Could not open codec "
    328               << (codec_context->codec ? codec_context->codec->name : "(NULL)")
    329               << " for " << in_path.value() << std::endl;
    330     return 1;
    331   }
    332 
    333   // Buffer used for audio decoding.
    334   scoped_ptr_malloc<AVFrame, media::ScopedPtrAVFree> audio_frame(
    335       avcodec_alloc_frame());
    336   if (!audio_frame) {
    337     std::cerr << "Error: avcodec_alloc_frame for "
    338               << in_path.value() << std::endl;
    339     return 1;
    340   }
    341 
    342   // Buffer used for video decoding.
    343   scoped_ptr_malloc<AVFrame, media::ScopedPtrAVFree> video_frame(
    344       avcodec_alloc_frame());
    345   if (!video_frame) {
    346     std::cerr << "Error: avcodec_alloc_frame for "
    347               << in_path.value() << std::endl;
    348     return 1;
    349   }
    350 
    351   // Remember size of video.
    352   int video_width = codec_context->width;
    353   int video_height = codec_context->height;
    354 
    355   // Stats collector.
    356   EnterTimingSection();
    357   std::vector<double> decode_times;
    358   decode_times.reserve(4096);
    359   // Parse through the entire stream until we hit EOF.
    360   base::TimeTicks start = base::TimeTicks::HighResNow();
    361   int frames = 0;
    362   int read_result = 0;
    363   do {
    364     read_result = av_read_frame(format_context, &packet);
    365 
    366     if (read_result < 0) {
    367       if (max_loops) {
    368         --max_loops;
    369       }
    370       if (max_loops > 0) {
    371         av_seek_frame(format_context, -1, 0, AVSEEK_FLAG_BACKWARD);
    372         read_result = 0;
    373         continue;
    374       }
    375       if (flush) {
    376         packet.stream_index = target_stream;
    377         packet.size = 0;
    378       } else {
    379         break;
    380       }
    381     }
    382 
    383     // Only decode packets from our target stream.
    384     if (packet.stream_index == target_stream) {
    385       int result = -1;
    386       if (target_codec == AVMEDIA_TYPE_AUDIO) {
    387         int size_out = 0;
    388         int got_audio = 0;
    389 
    390         avcodec_get_frame_defaults(audio_frame.get());
    391 
    392         base::TimeTicks decode_start = base::TimeTicks::HighResNow();
    393         result = avcodec_decode_audio4(codec_context, audio_frame.get(),
    394                                        &got_audio, &packet);
    395         base::TimeDelta delta = base::TimeTicks::HighResNow() - decode_start;
    396 
    397         if (got_audio) {
    398           size_out = av_samples_get_buffer_size(
    399               NULL, codec_context->channels, audio_frame->nb_samples,
    400               codec_context->sample_fmt, 1);
    401         }
    402 
    403         if (got_audio && size_out) {
    404           decode_times.push_back(delta.InMillisecondsF());
    405           ++frames;
    406           read_result = 0;  // Force continuation.
    407 
    408           if (output) {
    409             if (fwrite(audio_frame->data[0], 1, size_out, output) !=
    410                 static_cast<size_t>(size_out)) {
    411               std::cerr << "Error: Could not write "
    412                         << size_out << " bytes for " << in_path.value()
    413                         << std::endl;
    414               return 1;
    415             }
    416           }
    417 
    418           const uint8* u8_samples =
    419               reinterpret_cast<const uint8*>(audio_frame->data[0]);
    420           if (hash_djb2) {
    421             hash_value = DJB2Hash(u8_samples, size_out, hash_value);
    422           }
    423           if (hash_md5) {
    424             base::MD5Update(
    425                 &ctx,
    426                 base::StringPiece(reinterpret_cast<const char*>(u8_samples),
    427                                                                 size_out));
    428           }
    429         }
    430       } else if (target_codec == AVMEDIA_TYPE_VIDEO) {
    431         int got_picture = 0;
    432 
    433         avcodec_get_frame_defaults(video_frame.get());
    434 
    435         base::TimeTicks decode_start = base::TimeTicks::HighResNow();
    436         result = avcodec_decode_video2(codec_context, video_frame.get(),
    437                                        &got_picture, &packet);
    438         base::TimeDelta delta = base::TimeTicks::HighResNow() - decode_start;
    439 
    440         if (got_picture) {
    441           decode_times.push_back(delta.InMillisecondsF());
    442           ++frames;
    443           read_result = 0;  // Force continuation.
    444 
    445           for (int plane = 0; plane < 3; ++plane) {
    446             const uint8* source = video_frame->data[plane];
    447             const size_t source_stride = video_frame->linesize[plane];
    448             size_t bytes_per_line = codec_context->width;
    449             size_t copy_lines = codec_context->height;
    450             if (plane != 0) {
    451               switch (codec_context->pix_fmt) {
    452                 case PIX_FMT_YUV420P:
    453                 case PIX_FMT_YUVJ420P:
    454                   bytes_per_line /= 2;
    455                   copy_lines = (copy_lines + 1) / 2;
    456                   break;
    457                 case PIX_FMT_YUV422P:
    458                 case PIX_FMT_YUVJ422P:
    459                   bytes_per_line /= 2;
    460                   break;
    461                 case PIX_FMT_YUV444P:
    462                 case PIX_FMT_YUVJ444P:
    463                   break;
    464                 default:
    465                   std::cerr << "Error: Unknown video format "
    466                             << codec_context->pix_fmt;
    467                   return 1;
    468               }
    469             }
    470             if (output) {
    471               for (size_t i = 0; i < copy_lines; ++i) {
    472                 if (fwrite(source, 1, bytes_per_line, output) !=
    473                            bytes_per_line) {
    474                   std::cerr << "Error: Could not write data after "
    475                             << copy_lines << " lines for "
    476                             << in_path.value() << std::endl;
    477                   return 1;
    478                 }
    479                 source += source_stride;
    480               }
    481             }
    482             if (hash_djb2) {
    483               for (size_t i = 0; i < copy_lines; ++i) {
    484                 hash_value = DJB2Hash(source, bytes_per_line, hash_value);
    485                 source += source_stride;
    486               }
    487             }
    488             if (hash_md5) {
    489               for (size_t i = 0; i < copy_lines; ++i) {
    490                 base::MD5Update(
    491                     &ctx,
    492                     base::StringPiece(reinterpret_cast<const char*>(source),
    493                                       bytes_per_line));
    494                 source += source_stride;
    495               }
    496             }
    497           }
    498         }
    499       } else {
    500         NOTREACHED();
    501       }
    502 
    503       // Make sure our decoding went OK.
    504       if (result < 0) {
    505         std::cerr << "Error: avcodec_decode returned "
    506                   << result << " for " << in_path.value() << std::endl;
    507         return 1;
    508       }
    509     }
    510     // Free our packet.
    511     av_free_packet(&packet);
    512 
    513     if (max_frames && (frames >= max_frames))
    514       break;
    515   } while (read_result >= 0);
    516   base::TimeDelta total = base::TimeTicks::HighResNow() - start;
    517   LeaveTimingSection();
    518 
    519   // Clean up.
    520   if (output)
    521     file_util::CloseFile(output);
    522 
    523   // Calculate the sum of times.  Note that some of these may be zero.
    524   double sum = 0;
    525   for (size_t i = 0; i < decode_times.size(); ++i) {
    526     sum += decode_times[i];
    527   }
    528 
    529   double average = 0;
    530   double stddev = 0;
    531   double fps = 0;
    532   if (frames > 0) {
    533     // Calculate the average time per frame.
    534     average = sum / frames;
    535 
    536     // Calculate the sum of the squared differences.
    537     // Standard deviation will only be accurate if no threads are used.
    538     // TODO(fbarchard): Rethink standard deviation calculation.
    539     double squared_sum = 0;
    540     for (int i = 0; i < frames; ++i) {
    541       double difference = decode_times[i] - average;
    542       squared_sum += difference * difference;
    543     }
    544 
    545     // Calculate the standard deviation (jitter).
    546     stddev = sqrt(squared_sum / frames);
    547 
    548     // Calculate frames per second.
    549     fps = frames * 1000.0 / sum;
    550   }
    551 
    552   // Print our results.
    553   log_out->setf(std::ios::fixed);
    554   log_out->precision(2);
    555   *log_out << std::endl;
    556   *log_out << "     Frames:" << std::setw(11) << frames << std::endl;
    557   *log_out << "      Width:" << std::setw(11) << video_width << std::endl;
    558   *log_out << "     Height:" << std::setw(11) << video_height << std::endl;
    559   *log_out << "      Total:" << std::setw(11) << total.InMillisecondsF()
    560            << " ms" << std::endl;
    561   *log_out << "  Summation:" << std::setw(11) << sum
    562            << " ms" << std::endl;
    563   *log_out << "    Average:" << std::setw(11) << average
    564            << " ms" << std::endl;
    565   *log_out << "     StdDev:" << std::setw(11) << stddev
    566            << " ms" << std::endl;
    567   *log_out << "        FPS:" << std::setw(11) << fps
    568            << std::endl;
    569   if (hash_djb2) {
    570     *log_out << "  DJB2 Hash:" << std::setw(11) << hash_value
    571              << "  " << in_path.value() << std::endl;
    572   }
    573   if (hash_md5) {
    574     base::MD5Digest digest;  // The result of the computation.
    575     base::MD5Final(&digest, &ctx);
    576     *log_out << "   MD5 Hash: " << base::MD5DigestToBase16(digest)
    577              << " " << in_path.value() << std::endl;
    578   }
    579 #if defined(ENABLE_WINDOWS_EXCEPTIONS)
    580   } __except(EXCEPTION_EXECUTE_HANDLER) {
    581     *log_out << "  Exception:" << std::setw(11) << GetExceptionCode()
    582              << " " << in_path.value() << std::endl;
    583     return 1;
    584   }
    585 #endif
    586   CommandLine::Reset();
    587   return 0;
    588 }
    589