summaryrefslogtreecommitdiffstats
path: root/media/tools/media_bench
diff options
context:
space:
mode:
authorscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-25 18:43:53 +0000
committerscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-25 18:43:53 +0000
commit6e7c24ee5cd7ce88266ceae158c31c62d7498d3f (patch)
tree7fe0d691192959f1a31095aa35dc5efb147f88c8 /media/tools/media_bench
parenta999b0e58e1ca80435c7322df5cb229b64be113f (diff)
downloadchromium_src-6e7c24ee5cd7ce88266ceae158c31c62d7498d3f.zip
chromium_src-6e7c24ee5cd7ce88266ceae158c31c62d7498d3f.tar.gz
chromium_src-6e7c24ee5cd7ce88266ceae158c31c62d7498d3f.tar.bz2
Re-organizing all tools under /src/media to be consistent with the rest of the repository.
TEST=n/a BUG=n/a Review URL: http://codereview.chromium.org/431046 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@33089 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/tools/media_bench')
-rw-r--r--media/tools/media_bench/file_protocol.cc84
-rw-r--r--media/tools/media_bench/file_protocol.h14
-rw-r--r--media/tools/media_bench/media_bench.cc544
3 files changed, 642 insertions, 0 deletions
diff --git a/media/tools/media_bench/file_protocol.cc b/media/tools/media_bench/file_protocol.cc
new file mode 100644
index 0000000..f85fe33
--- /dev/null
+++ b/media/tools/media_bench/file_protocol.cc
@@ -0,0 +1,84 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/tools/media_bench/file_protocol.h"
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+
+#include "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "media/filters/ffmpeg_common.h"
+
+// warning C4996: 'open': The POSIX name for this item is deprecated.
+MSVC_PUSH_DISABLE_WARNING(4996)
+
+namespace {
+
+int GetHandle(URLContext *h) {
+ return static_cast<int>(reinterpret_cast<intptr_t>(h->priv_data));
+}
+
+// FFmpeg protocol interface.
+int OpenContext(URLContext* h, const char* filename, int flags) {
+ int access = O_RDONLY;
+ if (flags & URL_RDWR) {
+ access = O_CREAT | O_TRUNC | O_RDWR;
+ } else if (flags & URL_WRONLY) {
+ access = O_CREAT | O_TRUNC | O_WRONLY;
+ }
+#ifdef O_BINARY
+ access |= O_BINARY;
+#endif
+ int f = open(filename, access, 0666);
+ if (f == -1)
+ return AVERROR(ENOENT);
+ h->priv_data = reinterpret_cast<void*>(static_cast<intptr_t>(f));
+ h->is_streamed = false;
+ return 0;
+}
+
+int ReadContext(URLContext* h, unsigned char* buf, int size) {
+ return read(GetHandle(h), buf, size);
+}
+
+int WriteContext(URLContext* h, unsigned char* buf, int size) {
+ return write(GetHandle(h), buf, size);
+}
+
+offset_t SeekContext(URLContext* h, offset_t offset, int whence) {
+#if defined(OS_WIN)
+ return lseek(GetHandle(h), static_cast<long>(offset), whence);
+#else
+ return lseek(GetHandle(h), offset, whence);
+#endif
+}
+
+int CloseContext(URLContext* h) {
+ return close(GetHandle(h));
+}
+
+} // namespace
+
+MSVC_POP_WARNING()
+
+URLProtocol kFFmpegFileProtocol = {
+ "file",
+ &OpenContext,
+ &ReadContext,
+ &WriteContext,
+ &SeekContext,
+ &CloseContext,
+ NULL, // *next
+ NULL, // url_read_pause
+ NULL, // url_read_seek
+ &GetHandle
+};
diff --git a/media/tools/media_bench/file_protocol.h b/media/tools/media_bench/file_protocol.h
new file mode 100644
index 0000000..d4df2d3
--- /dev/null
+++ b/media/tools/media_bench/file_protocol.h
@@ -0,0 +1,14 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Implements a basic file I/O URLProtocol for FFmpeg. Since we don't build
+// FFmpeg binaries with protocols, we have to write our own.
+
+#ifndef MEDIA_TOOLS_MEDIA_BENCH_FILE_PROTOCOL_H_
+#define MEDIA_TOOLS_MEDIA_BENCH_FILE_PROTOCOL_H_
+
+struct URLProtocol;
+extern URLProtocol kFFmpegFileProtocol;
+
+#endif // MEDIA_TOOLS_MEDIA_BENCH_FILE_PROTOCOL_H_
diff --git a/media/tools/media_bench/media_bench.cc b/media/tools/media_bench/media_bench.cc
new file mode 100644
index 0000000..fffbaf3
--- /dev/null
+++ b/media/tools/media_bench/media_bench.cc
@@ -0,0 +1,544 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Standalone benchmarking application based on FFmpeg. This tool is used to
+// measure decoding performance between different FFmpeg compile and run-time
+// options. We also use this tool to measure performance regressions when
+// testing newer builds of FFmpeg from trunk.
+
+#include "build/build_config.h"
+
+// For pipe _setmode to binary
+#if defined(OS_WIN)
+#include <fcntl.h>
+#include <io.h>
+#endif
+
+#include <iomanip>
+#include <iostream>
+#include <string>
+
+#include "base/at_exit.h"
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/md5.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "media/base/djb2.h"
+#include "media/base/media.h"
+#include "media/filters/ffmpeg_common.h"
+#include "media/filters/ffmpeg_video_decoder.h"
+#include "media/tools/media_bench/file_protocol.h"
+
+namespace switches {
+const char kStream[] = "stream";
+const char kVideoThreads[] = "video-threads";
+const char kVerbose[] = "verbose";
+const char kFast2[] = "fast2";
+const char kSkip[] = "skip";
+const char kFlush[] = "flush";
+const char kDjb2[] = "djb2";
+const char kMd5[] = "md5";
+const char kFrames[] = "frames";
+const char kLoop[] = "loop";
+
+} // namespace switches
+
+#if defined(OS_WIN)
+// warning: disable warning about exception handler.
+#pragma warning(disable:4509)
+
+// Thread priorities to make benchmark more stable.
+
+void EnterTimingSection() {
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
+}
+
+void LeaveTimingSection() {
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
+}
+#else
+void EnterTimingSection() {
+ pthread_attr_t pta;
+ struct sched_param param;
+
+ pthread_attr_init(&pta);
+ memset(&param, 0, sizeof(param));
+ param.sched_priority = 78;
+ pthread_attr_setschedparam(&pta, &param);
+ pthread_attr_destroy(&pta);
+}
+
+void LeaveTimingSection() {
+}
+#endif
+
+int main(int argc, const char** argv) {
+ base::AtExitManager exit_manager;
+
+ CommandLine::Init(argc, argv);
+ const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
+
+ std::vector<std::wstring> filenames(cmd_line->GetLooseValues());
+ if (filenames.empty()) {
+ std::cerr << "Usage: media_bench [OPTIONS] FILE [DUMPFILE]\n"
+ << " --stream=[audio|video] "
+ << "Benchmark either the audio or video stream\n"
+ << " --video-threads=N "
+ << "Decode video using N threads\n"
+ << " --verbose=N "
+ << "Set FFmpeg log verbosity (-8 to 48)\n"
+ << " --frames=N "
+ << "Decode N frames\n"
+ << " --loop=N "
+ << "Loop N times\n"
+ << " --fast2 "
+ << "Enable fast2 flag\n"
+ << " --flush "
+ << "Flush last frame\n"
+ << " --djb2 (aka --hash) "
+ << "Hash decoded buffers (DJB2)\n"
+ << " --md5 "
+ << "Hash decoded buffers (MD5)\n"
+ << " --skip=[1|2|3] "
+ << "1=loop nonref, 2=loop, 3= frame nonref\n" << std::endl;
+ return 1;
+ }
+
+ // Initialize our media library (try loading DLLs, etc.) before continuing.
+ // We use an empty file path as the parameter to force searching of the
+ // default locations for necessary DLLs and DSOs.
+ if (media::InitializeMediaLibrary(FilePath()) == false) {
+ std::cerr << "Unable to initialize the media library.";
+ return 1;
+ }
+
+ // Retrieve command line options.
+ std::string in_path(WideToUTF8(filenames[0]));
+ std::string out_path;
+ if (filenames.size() > 1) {
+ out_path = WideToUTF8(filenames[1]);
+ }
+ CodecType target_codec = CODEC_TYPE_UNKNOWN;
+
+ // Determine whether to benchmark audio or video decoding.
+ std::string stream(cmd_line->GetSwitchValueASCII(switches::kStream));
+ if (!stream.empty()) {
+ if (stream.compare("audio") == 0) {
+ target_codec = CODEC_TYPE_AUDIO;
+ } else if (stream.compare("video") == 0) {
+ target_codec = CODEC_TYPE_VIDEO;
+ } else {
+ std::cerr << "Unknown --stream option " << stream << std::endl;
+ return 1;
+ }
+ }
+
+ // Determine number of threads to use for video decoding (optional).
+ int video_threads = 0;
+ std::string threads(cmd_line->GetSwitchValueASCII(switches::kVideoThreads));
+ if (!threads.empty() &&
+ !StringToInt(threads, &video_threads)) {
+ video_threads = 0;
+ }
+
+ // FFmpeg verbosity. See libavutil/log.h for values: -8 quiet..48 verbose.
+ int verbose_level = AV_LOG_FATAL;
+ std::string verbose(cmd_line->GetSwitchValueASCII(switches::kVerbose));
+ if (!verbose.empty() &&
+ !StringToInt(verbose, &verbose_level)) {
+ verbose_level = AV_LOG_FATAL;
+ }
+
+ // Determine number of frames to decode (optional).
+ int max_frames = 0;
+ std::string frames_opt(cmd_line->GetSwitchValueASCII(switches::kFrames));
+ if (!frames_opt.empty() &&
+ !StringToInt(frames_opt, &max_frames)) {
+ max_frames = 0;
+ }
+
+ // Determine number of times to loop (optional).
+ int max_loops = 0;
+ std::string loop_opt(cmd_line->GetSwitchValueASCII(switches::kLoop));
+ if (!loop_opt.empty() &&
+ !StringToInt(loop_opt, &max_loops)) {
+ max_loops = 0;
+ }
+
+ bool fast2 = false;
+ if (cmd_line->HasSwitch(switches::kFast2)) {
+ fast2 = true;
+ }
+
+ bool flush = false;
+ if (cmd_line->HasSwitch(switches::kFlush)) {
+ flush = true;
+ }
+
+ unsigned int hash_value = 5381u; // Seed for DJB2.
+ bool hash_djb2 = false;
+ if (cmd_line->HasSwitch(switches::kDjb2)) {
+ hash_djb2 = true;
+ }
+
+ MD5Context ctx; // Intermediate MD5 data: do not use
+ MD5Init(&ctx);
+ bool hash_md5 = false;
+ if (cmd_line->HasSwitch(switches::kMd5)) {
+ hash_md5 = true;
+ }
+
+ int skip = 0;
+ if (cmd_line->HasSwitch(switches::kSkip)) {
+ std::string skip_opt(cmd_line->GetSwitchValueASCII(switches::kSkip));
+ if (!StringToInt(skip_opt, &skip)) {
+ skip = 0;
+ }
+ }
+
+ std::ostream* log_out = &std::cout;
+#if defined(OS_WIN)
+ // Catch exceptions so this tool can be used in automated testing.
+ __try {
+#endif
+
+ // Register FFmpeg and attempt to open file.
+ avcodec_init();
+ av_log_set_level(verbose_level);
+ av_register_all();
+ av_register_protocol(&kFFmpegFileProtocol);
+ AVFormatContext* format_context = NULL;
+ if (av_open_input_file(&format_context, in_path.c_str(), NULL, 0, NULL) < 0) {
+ std::cerr << "Error: Could not open input for "
+ << in_path << std::endl;
+ return 1;
+ }
+
+ // Open output file.
+ FILE *output = NULL;
+ if (!out_path.empty()) {
+ // TODO(fbarchard): Add pipe:1 for piping to stderr.
+ if (!strncmp(out_path.c_str(), "pipe:", 5) ||
+ !strcmp(out_path.c_str(), "-")) {
+ output = stdout;
+ log_out = &std::cerr;
+#if defined(OS_WIN)
+ _setmode(_fileno(stdout), _O_BINARY);
+#endif
+ } else {
+ output = file_util::OpenFile(out_path.c_str(), "wb");
+ }
+ if (!output) {
+ std::cerr << "Error: Could not open output "
+ << out_path << std::endl;
+ return 1;
+ }
+ }
+
+ // Parse a little bit of the stream to fill out the format context.
+ if (av_find_stream_info(format_context) < 0) {
+ std::cerr << "Error: Could not find stream info for "
+ << in_path << std::endl;
+ return 1;
+ }
+
+ // Find our target stream.
+ int target_stream = -1;
+ for (size_t i = 0; i < format_context->nb_streams; ++i) {
+ AVCodecContext* codec_context = format_context->streams[i]->codec;
+ AVCodec* codec = avcodec_find_decoder(codec_context->codec_id);
+
+ // See if we found our target codec.
+ if (codec_context->codec_type == target_codec && target_stream < 0) {
+ *log_out << "* ";
+ target_stream = i;
+ } else {
+ *log_out << " ";
+ }
+
+ if (!codec || (codec_context->codec_type == CODEC_TYPE_UNKNOWN)) {
+ *log_out << "Stream #" << i << ": Unknown" << std::endl;
+ } else {
+ // Print out stream information
+ *log_out << "Stream #" << i << ": " << codec->name << " ("
+ << codec->long_name << ")" << std::endl;
+ }
+ }
+
+ // Only continue if we found our target stream.
+ if (target_stream < 0) {
+ std::cerr << "Error: Could not find target stream "
+ << target_stream << " for " << in_path << std::endl;
+ return 1;
+ }
+
+ // Prepare FFmpeg structures.
+ AVPacket packet;
+ AVCodecContext* codec_context = format_context->streams[target_stream]->codec;
+ AVCodec* codec = avcodec_find_decoder(codec_context->codec_id);
+
+ // Only continue if we found our codec.
+ if (!codec) {
+ std::cerr << "Error: Could not find codec for "
+ << in_path << std::endl;
+ return 1;
+ }
+
+ if (skip == 1) {
+ codec_context->skip_loop_filter = AVDISCARD_NONREF;
+ } else if (skip == 2) {
+ codec_context->skip_loop_filter = AVDISCARD_ALL;
+ } else if (skip == 3) {
+ codec_context->skip_loop_filter = AVDISCARD_ALL;
+ codec_context->skip_frame = AVDISCARD_NONREF;
+ }
+ if (fast2) {
+ codec_context->flags2 |= CODEC_FLAG2_FAST;
+ }
+
+ // Initialize threaded decode.
+ if (target_codec == CODEC_TYPE_VIDEO && video_threads > 0) {
+ if (avcodec_thread_init(codec_context, video_threads) < 0) {
+ std::cerr << "Warning: Could not initialize threading!\n"
+ << "Did you build with pthread/w32thread support?" << std::endl;
+ }
+ }
+
+ // Initialize our codec.
+ if (avcodec_open(codec_context, codec) < 0) {
+ std::cerr << "Error: Could not open codec "
+ << codec_context->codec->name << " for "
+ << in_path << std::endl;
+ return 1;
+ }
+
+ // Buffer used for audio decoding.
+ scoped_ptr_malloc<int16, media::ScopedPtrAVFree> samples(
+ reinterpret_cast<int16*>(av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE)));
+
+ // Buffer used for video decoding.
+ scoped_ptr_malloc<AVFrame, media::ScopedPtrAVFree> frame(
+ avcodec_alloc_frame());
+ if (!frame.get()) {
+ std::cerr << "Error: avcodec_alloc_frame for "
+ << in_path << std::endl;
+ return 1;
+ }
+
+ // Stats collector.
+ EnterTimingSection();
+ std::vector<double> decode_times;
+ decode_times.reserve(4096);
+ // Parse through the entire stream until we hit EOF.
+ base::TimeTicks start = base::TimeTicks::HighResNow();
+ int frames = 0;
+ int read_result = 0;
+ do {
+ read_result = av_read_frame(format_context, &packet);
+
+ if (read_result < 0) {
+ if (max_loops) {
+ --max_loops;
+ }
+ if (max_loops > 0) {
+ av_seek_frame(format_context, -1, 0, AVSEEK_FLAG_BACKWARD);
+ read_result = 0;
+ continue;
+ }
+ if (flush) {
+ packet.stream_index = target_stream;
+ packet.size = 0;
+ } else {
+ break;
+ }
+ }
+
+ // Only decode packets from our target stream.
+ if (packet.stream_index == target_stream) {
+ int result = -1;
+ if (target_codec == CODEC_TYPE_AUDIO) {
+ int size_out = AVCODEC_MAX_AUDIO_FRAME_SIZE;
+
+ base::TimeTicks decode_start = base::TimeTicks::HighResNow();
+ result = avcodec_decode_audio3(codec_context, samples.get(), &size_out,
+ &packet);
+ base::TimeDelta delta = base::TimeTicks::HighResNow() - decode_start;
+
+ if (size_out) {
+ decode_times.push_back(delta.InMillisecondsF());
+ ++frames;
+ read_result = 0; // Force continuation.
+
+ if (output) {
+ if (fwrite(samples.get(), 1, size_out, output) !=
+ static_cast<size_t>(size_out)) {
+ std::cerr << "Error: Could not write "
+ << size_out << " bytes for " << in_path << std::endl;
+ return 1;
+ }
+ }
+
+ const uint8* u8_samples =
+ reinterpret_cast<const uint8*>(samples.get());
+ if (hash_djb2) {
+ hash_value = DJB2Hash(u8_samples, size_out, hash_value);
+ }
+ if (hash_md5) {
+ MD5Update(&ctx, u8_samples, size_out);
+ }
+ }
+ } else if (target_codec == CODEC_TYPE_VIDEO) {
+ int got_picture = 0;
+
+ base::TimeTicks decode_start = base::TimeTicks::HighResNow();
+ result = avcodec_decode_video2(codec_context, frame.get(),
+ &got_picture, &packet);
+ base::TimeDelta delta = base::TimeTicks::HighResNow() - decode_start;
+
+ if (got_picture) {
+ decode_times.push_back(delta.InMillisecondsF());
+ ++frames;
+ read_result = 0; // Force continuation.
+
+ for (int plane = 0; plane < 3; ++plane) {
+ const uint8* source = frame->data[plane];
+ const size_t source_stride = frame->linesize[plane];
+ size_t bytes_per_line = codec_context->width;
+ size_t copy_lines = codec_context->height;
+ if (plane != 0) {
+ switch (codec_context->pix_fmt) {
+ case PIX_FMT_YUV420P:
+ case PIX_FMT_YUVJ420P:
+ bytes_per_line /= 2;
+ copy_lines = (copy_lines + 1) / 2;
+ break;
+ case PIX_FMT_YUV422P:
+ case PIX_FMT_YUVJ422P:
+ bytes_per_line /= 2;
+ break;
+ case PIX_FMT_YUV444P:
+ case PIX_FMT_YUVJ444P:
+ break;
+ default:
+ std::cerr << "Error: Unknown video format "
+ << codec_context->pix_fmt;
+ return 1;
+ }
+ }
+ if (output) {
+ for (size_t i = 0; i < copy_lines; ++i) {
+ if (fwrite(source, 1, bytes_per_line, output) !=
+ bytes_per_line) {
+ std::cerr << "Error: Could not write data after "
+ << copy_lines << " lines for "
+ << in_path << std::endl;
+ return 1;
+ }
+ source += source_stride;
+ }
+ }
+ if (hash_djb2) {
+ for (size_t i = 0; i < copy_lines; ++i) {
+ hash_value = DJB2Hash(source, bytes_per_line, hash_value);
+ source += source_stride;
+ }
+ }
+ if (hash_md5) {
+ for (size_t i = 0; i < copy_lines; ++i) {
+ MD5Update(&ctx, reinterpret_cast<const uint8*>(source),
+ bytes_per_line);
+ source += source_stride;
+ }
+ }
+ }
+ }
+ } else {
+ NOTREACHED();
+ }
+
+ // Make sure our decoding went OK.
+ if (result < 0) {
+ std::cerr << "Error: avcodec_decode returned "
+ << result << " for " << in_path << std::endl;
+ return 1;
+ }
+ }
+ // Free our packet.
+ av_free_packet(&packet);
+
+ if (max_frames && (frames >= max_frames))
+ break;
+ } while (read_result >= 0);
+ base::TimeDelta total = base::TimeTicks::HighResNow() - start;
+ LeaveTimingSection();
+
+ // Clean up.
+ if (output)
+ file_util::CloseFile(output);
+ if (codec_context)
+ avcodec_close(codec_context);
+ if (format_context)
+ av_close_input_file(format_context);
+
+ // Calculate the sum of times. Note that some of these may be zero.
+ double sum = 0;
+ for (size_t i = 0; i < decode_times.size(); ++i) {
+ sum += decode_times[i];
+ }
+
+ // Print our results.
+ log_out->setf(std::ios::fixed);
+ log_out->precision(2);
+ *log_out << std::endl;
+ *log_out << " Frames:" << std::setw(11) << frames
+ << std::endl;
+ *log_out << " Total:" << std::setw(11) << total.InMillisecondsF()
+ << " ms" << std::endl;
+ *log_out << " Summation:" << std::setw(11) << sum
+ << " ms" << std::endl;
+
+ if (frames > 0) {
+ // Calculate the average time per frame.
+ double average = sum / frames;
+
+ // Calculate the sum of the squared differences.
+ // Standard deviation will only be accurate if no threads are used.
+ // TODO(fbarchard): Rethink standard deviation calculation.
+ double squared_sum = 0;
+ for (int i = 0; i < frames; ++i) {
+ double difference = decode_times[i] - average;
+ squared_sum += difference * difference;
+ }
+
+ // Calculate the standard deviation (jitter).
+ double stddev = sqrt(squared_sum / frames);
+
+ *log_out << " Average:" << std::setw(11) << average
+ << " ms" << std::endl;
+ *log_out << " StdDev:" << std::setw(11) << stddev
+ << " ms" << std::endl;
+ }
+ if (hash_djb2) {
+ *log_out << " DJB2 Hash:" << std::setw(11) << hash_value
+ << " " << in_path << std::endl;
+ }
+ if (hash_md5) {
+ MD5Digest digest; // The result of the computation.
+ MD5Final(&digest, &ctx);
+ *log_out << " MD5 Hash: " << MD5DigestToBase16(digest)
+ << " " << in_path << std::endl;
+ }
+#if defined(OS_WIN)
+ } __except(EXCEPTION_EXECUTE_HANDLER) {
+ *log_out << " Exception:" << std::setw(11) << GetExceptionCode()
+ << " " << in_path << std::endl;
+ return 1;
+ }
+#endif
+ CommandLine::Reset();
+ return 0;
+}