diff options
author | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-25 18:43:53 +0000 |
---|---|---|
committer | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-25 18:43:53 +0000 |
commit | 6e7c24ee5cd7ce88266ceae158c31c62d7498d3f (patch) | |
tree | 7fe0d691192959f1a31095aa35dc5efb147f88c8 /media/tools/media_bench | |
parent | a999b0e58e1ca80435c7322df5cb229b64be113f (diff) | |
download | chromium_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.cc | 84 | ||||
-rw-r--r-- | media/tools/media_bench/file_protocol.h | 14 | ||||
-rw-r--r-- | media/tools/media_bench/media_bench.cc | 544 |
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(¶m, 0, sizeof(param)); + param.sched_priority = 78; + pthread_attr_setschedparam(&pta, ¶m); + 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; +} |