| OLD | NEW |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // Standalone benchmarking application based on FFmpeg. This tool is used to | 5 // Standalone benchmarking application based on FFmpeg. This tool is used to |
| 6 // measure decoding performance between different FFmpeg compile and run-time | 6 // measure decoding performance between different FFmpeg compile and run-time |
| 7 // options. We also use this tool to measure performance regressions when | 7 // options. We also use this tool to measure performance regressions when |
| 8 // testing newer builds of FFmpeg from trunk. | 8 // testing newer builds of FFmpeg from trunk. |
| 9 // |
| 10 // This tool requires FFMPeg DLL's built with --enable-protocol=file. |
| 9 | 11 |
| 10 #include <iomanip> | 12 #include <iomanip> |
| 11 #include <iostream> | 13 #include <iostream> |
| 12 #include <string> | 14 #include <string> |
| 13 | 15 |
| 14 #include "base/at_exit.h" | 16 #include "base/at_exit.h" |
| 15 #include "base/basictypes.h" | 17 #include "base/basictypes.h" |
| 16 #include "base/command_line.h" | 18 #include "base/command_line.h" |
| 17 #include "base/file_path.h" | 19 #include "base/file_path.h" |
| 18 #include "base/logging.h" | 20 #include "base/logging.h" |
| 19 #include "base/string_util.h" | 21 #include "base/string_util.h" |
| 20 #include "base/time.h" | 22 #include "base/time.h" |
| 21 #include "media/base/media.h" | 23 #include "media/base/media.h" |
| 22 #include "media/filters/ffmpeg_common.h" | 24 #include "media/filters/ffmpeg_common.h" |
| 23 | 25 |
| 24 namespace switches { | 26 namespace switches { |
| 25 const wchar_t kStream[] = L"stream"; | 27 const wchar_t kStream[] = L"stream"; |
| 26 const wchar_t kVideoThreads[] = L"video-threads"; | 28 const wchar_t kVideoThreads[] = L"video-threads"; |
| 27 const wchar_t kFast2[] = L"fast2"; | 29 const wchar_t kFast2[] = L"fast2"; |
| 28 const wchar_t kSkip[] = L"skip"; | 30 const wchar_t kSkip[] = L"skip"; |
| 31 const wchar_t kFlush[] = L"flush"; |
| 29 } // namespace switches | 32 } // namespace switches |
| 30 | 33 |
| 31 int main(int argc, const char** argv) { | 34 int main(int argc, const char** argv) { |
| 32 base::AtExitManager exit_manager; | 35 base::AtExitManager exit_manager; |
| 33 | 36 |
| 34 CommandLine::Init(argc, argv); | 37 CommandLine::Init(argc, argv); |
| 35 const CommandLine* cmd_line = CommandLine::ForCurrentProcess(); | 38 const CommandLine* cmd_line = CommandLine::ForCurrentProcess(); |
| 36 | 39 |
| 37 std::vector<std::wstring> filenames(cmd_line->GetLooseValues()); | 40 std::vector<std::wstring> filenames(cmd_line->GetLooseValues()); |
| 38 if (filenames.empty()) { | 41 if (filenames.empty()) { |
| 39 std::cerr << "Usage: media_bench [OPTIONS] FILE\n" | 42 std::cerr << "Usage: media_bench [OPTIONS] FILE\n" |
| 40 << " --stream=[audio|video] " | 43 << " --stream=[audio|video] " |
| 41 << "Benchmark either the audio or video stream\n" | 44 << "Benchmark either the audio or video stream\n" |
| 42 << " --video-threads=N " | 45 << " --video-threads=N " |
| 43 << "Decode video using N threads\n" | 46 << "Decode video using N threads\n" |
| 44 << " --fast2 " | 47 << " --fast2 " |
| 45 << "Enable fast2 flag\n" | 48 << "Enable fast2 flag\n" |
| 49 << " --flush " |
| 50 << "Flush last frame\n" |
| 46 << " --skip=[1|2|3] " | 51 << " --skip=[1|2|3] " |
| 47 << "1=loop nonref, 2=loop, 3= frame nonref" << std::endl; | 52 << "1=loop nonref, 2=loop, 3= frame nonref\n" << std::endl; |
| 48 return 1; | 53 return 1; |
| 49 } | 54 } |
| 50 | 55 |
| 51 // Initialize our media library (try loading DLLs, etc.) before continuing. | 56 // Initialize our media library (try loading DLLs, etc.) before continuing. |
| 52 // We use an empty file path as the parameter to force searching of the | 57 // We use an empty file path as the parameter to force searching of the |
| 53 // default locations for necessary DLLs and DSOs. | 58 // default locations for necessary DLLs and DSOs. |
| 54 if (media::InitializeMediaLibrary(FilePath()) == false) { | 59 if (media::InitializeMediaLibrary(FilePath()) == false) { |
| 55 std::cerr << "Unable to initialize the media library."; | 60 std::cerr << "Unable to initialize the media library."; |
| 56 return 1; | 61 return 1; |
| 57 } | 62 } |
| (...skipping 21 matching lines...) Expand all Loading... |
| 79 if (!threads.empty() && | 84 if (!threads.empty() && |
| 80 !StringToInt(WideToUTF16Hack(threads), &video_threads)) { | 85 !StringToInt(WideToUTF16Hack(threads), &video_threads)) { |
| 81 video_threads = 0; | 86 video_threads = 0; |
| 82 } | 87 } |
| 83 | 88 |
| 84 bool fast2 = false; | 89 bool fast2 = false; |
| 85 if (cmd_line->HasSwitch(switches::kFast2)) { | 90 if (cmd_line->HasSwitch(switches::kFast2)) { |
| 86 fast2 = true; | 91 fast2 = true; |
| 87 } | 92 } |
| 88 | 93 |
| 94 bool flush = false; |
| 95 if (cmd_line->HasSwitch(switches::kFlush)) { |
| 96 flush = true; |
| 97 } |
| 98 |
| 89 int skip = 0; | 99 int skip = 0; |
| 90 if (cmd_line->HasSwitch(switches::kSkip)) { | 100 if (cmd_line->HasSwitch(switches::kSkip)) { |
| 91 std::wstring skip_opt(cmd_line->GetSwitchValue(switches::kSkip)); | 101 std::wstring skip_opt(cmd_line->GetSwitchValue(switches::kSkip)); |
| 92 if (!StringToInt(WideToUTF16Hack(skip_opt), &skip)) { | 102 if (!StringToInt(WideToUTF16Hack(skip_opt), &skip)) { |
| 93 skip = 0; | 103 skip = 0; |
| 94 } | 104 } |
| 95 } | 105 } |
| 96 | 106 |
| 97 // Register FFmpeg and attempt to open file. | 107 // Register FFmpeg and attempt to open file. |
| 98 avcodec_init(); | 108 avcodec_init(); |
| (...skipping 17 matching lines...) Expand all Loading... |
| 116 AVCodec* codec = avcodec_find_decoder(codec_context->codec_id); | 126 AVCodec* codec = avcodec_find_decoder(codec_context->codec_id); |
| 117 | 127 |
| 118 // See if we found our target codec. | 128 // See if we found our target codec. |
| 119 if (codec_context->codec_type == target_codec && target_stream < 0) { | 129 if (codec_context->codec_type == target_codec && target_stream < 0) { |
| 120 std::cout << "* "; | 130 std::cout << "* "; |
| 121 target_stream = i; | 131 target_stream = i; |
| 122 } else { | 132 } else { |
| 123 std::cout << " "; | 133 std::cout << " "; |
| 124 } | 134 } |
| 125 | 135 |
| 126 // Print out stream information | 136 if (codec_context->codec_type == CODEC_TYPE_UNKNOWN) { |
| 127 std::cout << "Stream #" << i << ": " << codec->name << " (" | 137 std::cout << "Stream #" << i << ": Unknown" << std::endl; |
| 128 << codec->long_name << ")" << std::endl; | 138 } else { |
| 139 // Print out stream information |
| 140 std::cout << "Stream #" << i << ": " << codec->name << " (" |
| 141 << codec->long_name << ")" << std::endl; |
| 142 } |
| 129 } | 143 } |
| 130 | 144 |
| 131 // Only continue if we found our target stream. | 145 // Only continue if we found our target stream. |
| 132 if (target_stream < 0) { | 146 if (target_stream < 0) { |
| 133 return 1; | 147 return 1; |
| 134 } | 148 } |
| 135 | 149 |
| 136 // Prepare FFmpeg structures. | 150 // Prepare FFmpeg structures. |
| 137 AVPacket packet; | 151 AVPacket packet; |
| 138 AVCodecContext* codec_context = format_context->streams[target_stream]->codec; | 152 AVCodecContext* codec_context = format_context->streams[target_stream]->codec; |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 172 // Buffer used for video decoding. | 186 // Buffer used for video decoding. |
| 173 AVFrame* frame = avcodec_alloc_frame(); | 187 AVFrame* frame = avcodec_alloc_frame(); |
| 174 if (!frame) { | 188 if (!frame) { |
| 175 std::cerr << "Could not allocate an AVFrame" << std::endl; | 189 std::cerr << "Could not allocate an AVFrame" << std::endl; |
| 176 return 1; | 190 return 1; |
| 177 } | 191 } |
| 178 | 192 |
| 179 // Stats collector. | 193 // Stats collector. |
| 180 std::vector<double> decode_times; | 194 std::vector<double> decode_times; |
| 181 decode_times.reserve(4096); | 195 decode_times.reserve(4096); |
| 182 | |
| 183 // Parse through the entire stream until we hit EOF. | 196 // Parse through the entire stream until we hit EOF. |
| 184 base::TimeTicks start = base::TimeTicks::HighResNow(); | 197 base::TimeTicks start = base::TimeTicks::HighResNow(); |
| 185 while (av_read_frame(format_context, &packet) >= 0) { | 198 size_t frames = 0; |
| 199 int read_result = 0; |
| 200 do { |
| 201 read_result = av_read_frame(format_context, &packet); |
| 202 |
| 203 if (read_result < 0) { |
| 204 if (flush) { |
| 205 packet.stream_index = target_stream; |
| 206 packet.size = 0; |
| 207 } else { |
| 208 break; |
| 209 } |
| 210 } |
| 211 |
| 186 // Only decode packets from our target stream. | 212 // Only decode packets from our target stream. |
| 187 if (packet.stream_index == target_stream) { | 213 if (packet.stream_index == target_stream) { |
| 188 int result = -1; | 214 int result = -1; |
| 189 base::TimeTicks decode_start = base::TimeTicks::HighResNow(); | 215 base::TimeTicks decode_start = base::TimeTicks::HighResNow(); |
| 190 if (target_codec == CODEC_TYPE_AUDIO) { | 216 if (target_codec == CODEC_TYPE_AUDIO) { |
| 191 int size_out = AVCODEC_MAX_AUDIO_FRAME_SIZE; | 217 int size_out = AVCODEC_MAX_AUDIO_FRAME_SIZE; |
| 192 result = avcodec_decode_audio3(codec_context, samples, &size_out, | 218 result = avcodec_decode_audio3(codec_context, samples, &size_out, |
| 193 &packet); | 219 &packet); |
| 220 if (size_out) { |
| 221 ++frames; |
| 222 read_result = 0; // Force continuation. |
| 223 } |
| 194 } else if (target_codec == CODEC_TYPE_VIDEO) { | 224 } else if (target_codec == CODEC_TYPE_VIDEO) { |
| 195 int got_picture = 0; | 225 int got_picture = 0; |
| 196 result = avcodec_decode_video2(codec_context, frame, &got_picture, | 226 result = avcodec_decode_video2(codec_context, frame, &got_picture, |
| 197 &packet); | 227 &packet); |
| 228 if (got_picture) { |
| 229 ++frames; |
| 230 read_result = 0; // Force continuation. |
| 231 } |
| 198 } else { | 232 } else { |
| 199 NOTREACHED(); | 233 NOTREACHED(); |
| 200 } | 234 } |
| 201 base::TimeDelta delta = base::TimeTicks::HighResNow() - decode_start; | 235 base::TimeDelta delta = base::TimeTicks::HighResNow() - decode_start; |
| 236 |
| 202 decode_times.push_back(delta.InMillisecondsF()); | 237 decode_times.push_back(delta.InMillisecondsF()); |
| 203 | 238 |
| 204 // Make sure our decoding went OK. | 239 // Make sure our decoding went OK. |
| 205 if (result < 0) { | 240 if (result < 0) { |
| 206 std::cerr << "Error while decoding" << std::endl; | 241 std::cerr << "Error while decoding" << std::endl; |
| 207 return 1; | 242 return 1; |
| 208 } | 243 } |
| 209 } | 244 } |
| 210 | |
| 211 // Free our packet. | 245 // Free our packet. |
| 212 av_free_packet(&packet); | 246 av_free_packet(&packet); |
| 213 } | 247 } while (read_result >= 0); |
| 214 base::TimeDelta total = base::TimeTicks::HighResNow() - start; | 248 base::TimeDelta total = base::TimeTicks::HighResNow() - start; |
| 215 | 249 |
| 216 // Calculate the sum. The numbers are very consistent and the we're not too | 250 // Calculate the sum of times. Note that some of these may be zero. |
| 217 // worried about floating point error here. | |
| 218 double sum = 0; | 251 double sum = 0; |
| 219 for (size_t i = 0; i < decode_times.size(); ++i) { | 252 for (size_t i = 0; i < decode_times.size(); ++i) { |
| 220 sum += decode_times[i]; | 253 sum += decode_times[i]; |
| 221 } | 254 } |
| 222 | 255 |
| 223 // Calculate the average. | 256 // Print our results. |
| 224 double average = sum / decode_times.size(); | |
| 225 | |
| 226 // Calculate the sum of the squared differences. | |
| 227 double squared_sum = 0; | |
| 228 for (size_t i = 0; i < decode_times.size(); ++i) { | |
| 229 double difference = decode_times[i] - average; | |
| 230 squared_sum += difference * difference; | |
| 231 } | |
| 232 | |
| 233 // Calculate the standard deviation (jitter). | |
| 234 double stddev = sqrt(squared_sum / decode_times.size()); | |
| 235 | |
| 236 // Print our results. | |
| 237 std::cout.setf(std::ios::fixed); | 257 std::cout.setf(std::ios::fixed); |
| 238 std::cout.precision(3); | 258 std::cout.precision(3); |
| 239 std::cout << std::endl; | 259 std::cout << std::endl; |
| 240 std::cout << " Frames:" << std::setw(10) << decode_times.size() | 260 std::cout << " Frames:" << std::setw(10) << frames |
| 241 << std::endl; | 261 << std::endl; |
| 242 std::cout << " Total:" << std::setw(10) << total.InMillisecondsF() | 262 std::cout << " Total:" << std::setw(10) << total.InMillisecondsF() |
| 243 << " ms" << std::endl; | 263 << " ms" << std::endl; |
| 244 std::cout << " Summation:" << std::setw(10) << sum | 264 std::cout << " Summation:" << std::setw(10) << sum |
| 245 << " ms" << std::endl; | 265 << " ms" << std::endl; |
| 246 std::cout << " Average:" << std::setw(10) << average | 266 |
| 247 << " ms" << std::endl; | 267 if (frames > 0u) { |
| 248 std::cout << " StdDev:" << std::setw(10) << stddev | 268 // Calculate the average time per frame. |
| 249 << " ms" << std::endl; | 269 double average = sum / frames; |
| 270 |
| 271 // Calculate the sum of the squared differences. |
| 272 // Standard deviation will only be accurate if no threads are used. |
| 273 // TODO(fbarchard): Rethink standard deviation calculation. |
| 274 double squared_sum = 0; |
| 275 for (size_t i = 0; i < frames; ++i) { |
| 276 double difference = decode_times[i] - average; |
| 277 squared_sum += difference * difference; |
| 278 } |
| 279 |
| 280 // Calculate the standard deviation (jitter). |
| 281 double stddev = sqrt(squared_sum / frames); |
| 282 |
| 283 std::cout << " Average:" << std::setw(10) << average |
| 284 << " ms" << std::endl; |
| 285 std::cout << " StdDev:" << std::setw(10) << stddev |
| 286 << " ms" << std::endl; |
| 287 } |
| 250 return 0; | 288 return 0; |
| 251 } | 289 } |
| OLD | NEW |