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 |