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// Software qualification test for FFmpeg.  This test is used to certify that
6// software decoding quality and performance of FFmpeg meets a mimimum
7// standard.
8
9#include <iomanip>
10#include <iostream>
11#include <string>
12
13#include "base/at_exit.h"
14#include "base/basictypes.h"
15#include "base/command_line.h"
16#include "base/file_util.h"
17#include "base/files/file_path.h"
18#include "base/files/memory_mapped_file.h"
19#include "base/logging.h"
20#include "base/md5.h"
21#include "base/path_service.h"
22#include "base/strings/string_util.h"
23#include "base/strings/utf_string_conversions.h"
24#include "base/time/time.h"
25#include "media/base/djb2.h"
26#include "media/base/media.h"
27#include "media/ffmpeg/ffmpeg_common.h"
28#include "media/filters/ffmpeg_glue.h"
29#include "media/filters/ffmpeg_video_decoder.h"
30#include "media/filters/in_memory_url_protocol.h"
31
32#ifdef DEBUG
33#define SHOW_VERBOSE 1
34#else
35#define SHOW_VERBOSE 0
36#endif
37
38#if defined(OS_WIN)
39
40// Enable to build with exception handler
41//#define ENABLE_WINDOWS_EXCEPTIONS 1
42
43#ifdef ENABLE_WINDOWS_EXCEPTIONS
44// warning: disable warning about exception handler.
45#pragma warning(disable:4509)
46#endif
47
48// Thread priorities to make benchmark more stable.
49
50void EnterTimingSection() {
51  SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
52}
53
54void LeaveTimingSection() {
55  SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
56}
57#else
58void EnterTimingSection() {
59  pthread_attr_t pta;
60  struct sched_param param;
61
62  pthread_attr_init(&pta);
63  memset(&param, 0, sizeof(param));
64  param.sched_priority = 78;
65  pthread_attr_setschedparam(&pta, &param);
66  pthread_attr_destroy(&pta);
67}
68
69void LeaveTimingSection() {
70}
71#endif
72
73int main(int argc, const char** argv) {
74  base::AtExitManager exit_manager;
75
76  CommandLine::Init(argc, argv);
77  const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
78
79  const CommandLine::StringVector& filenames = cmd_line->GetArgs();
80
81  if (filenames.empty()) {
82    std::cerr << "Usage: " << argv[0] << " MEDIAFILE" << std::endl;
83    return 1;
84  }
85
86  // Initialize our media library (try loading DLLs, etc.) before continuing.
87  base::FilePath media_path;
88  PathService::Get(base::DIR_MODULE, &media_path);
89  if (!media::InitializeMediaLibrary(media_path)) {
90    std::cerr << "Unable to initialize the media library.";
91    return 1;
92  }
93
94  // Retrieve command line options.
95  base::FilePath in_path(filenames[0]);
96  base::FilePath out_path;
97  if (filenames.size() > 1)
98    out_path = base::FilePath(filenames[1]);
99
100  // Default flags that match Chrome defaults.
101  int video_threads = 2;
102  int max_frames = 0;
103  int max_loops = 0;
104  bool flush = false;
105
106  unsigned int hash_value = 5381u;  // Seed for DJB2.
107  bool hash_djb2 = false;
108
109  base::MD5Context ctx;  // Intermediate MD5 data: do not use
110  base::MD5Init(&ctx);
111  bool hash_md5 = false;
112
113  std::ostream* log_out = &std::cout;
114#if defined(ENABLE_WINDOWS_EXCEPTIONS)
115  // Catch exceptions so this tool can be used in automated testing.
116  __try {
117#endif
118
119  base::MemoryMappedFile file_data;
120  file_data.Initialize(in_path);
121  media::InMemoryUrlProtocol protocol(
122      file_data.data(), file_data.length(), false);
123
124  // Register FFmpeg and attempt to open file.
125  media::FFmpegGlue glue(&protocol);
126  if (!glue.OpenContext()) {
127    std::cerr << "Error: Could not open input for "
128              << in_path.value() << std::endl;
129    return 1;
130  }
131
132  AVFormatContext* format_context = glue.format_context();
133
134  // Open output file.
135  FILE *output = NULL;
136  if (!out_path.empty()) {
137    output = file_util::OpenFile(out_path, "wb");
138    if (!output) {
139      std::cerr << "Error: Could not open output "
140                << out_path.value() << std::endl;
141      return 1;
142    }
143  }
144
145  // Parse a little bit of the stream to fill out the format context.
146  if (avformat_find_stream_info(format_context, NULL) < 0) {
147    std::cerr << "Error: Could not find stream info for "
148              << in_path.value() << std::endl;
149    return 1;
150  }
151
152  // Find our target stream(s)
153  int video_stream = -1;
154  int audio_stream = -1;
155  for (size_t i = 0; i < format_context->nb_streams; ++i) {
156    AVCodecContext* codec_context = format_context->streams[i]->codec;
157
158    if (codec_context->codec_type == AVMEDIA_TYPE_VIDEO && video_stream < 0) {
159#if SHOW_VERBOSE
160      *log_out << "V ";
161#endif
162      video_stream = i;
163    } else {
164      if (codec_context->codec_type == AVMEDIA_TYPE_AUDIO && audio_stream < 0) {
165#if SHOW_VERBOSE
166        *log_out << "A ";
167#endif
168        audio_stream = i;
169      } else {
170#if SHOW_VERBOSE
171      *log_out << "  ";
172#endif
173      }
174    }
175
176#if SHOW_VERBOSE
177    AVCodec* codec = avcodec_find_decoder(codec_context->codec_id);
178    if (!codec || (codec_context->codec_type == AVMEDIA_TYPE_UNKNOWN)) {
179      *log_out << "Stream #" << i << ": Unknown" << std::endl;
180    } else {
181      // Print out stream information
182      *log_out << "Stream #" << i << ": " << codec->name << " ("
183               << codec->long_name << ")" << std::endl;
184    }
185#endif
186  }
187  int target_stream = video_stream;
188  AVMediaType target_codec = AVMEDIA_TYPE_VIDEO;
189  if (target_stream < 0) {
190    target_stream = audio_stream;
191    target_codec = AVMEDIA_TYPE_AUDIO;
192  }
193
194  // Only continue if we found our target stream.
195  if (target_stream < 0) {
196    std::cerr << "Error: Could not find target stream "
197              << target_stream << " for " << in_path.value() << std::endl;
198    return 1;
199  }
200
201  // Prepare FFmpeg structures.
202  AVPacket packet;
203  AVCodecContext* codec_context = format_context->streams[target_stream]->codec;
204  AVCodec* codec = avcodec_find_decoder(codec_context->codec_id);
205
206  // Only continue if we found our codec.
207  if (!codec) {
208    std::cerr << "Error: Could not find codec for "
209              << in_path.value() << std::endl;
210    return 1;
211  }
212
213  codec_context->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
214
215  // Initialize threaded decode.
216  if (target_codec == AVMEDIA_TYPE_VIDEO && video_threads > 0) {
217    codec_context->thread_count = video_threads;
218  }
219
220  // Initialize our codec.
221  if (avcodec_open2(codec_context, codec, NULL) < 0) {
222    std::cerr << "Error: Could not open codec "
223              << codec_context->codec->name << " for "
224              << in_path.value() << std::endl;
225    return 1;
226  }
227
228  // Buffer used for audio decoding.
229  scoped_ptr_malloc<AVFrame, media::ScopedPtrAVFree> audio_frame(
230      avcodec_alloc_frame());
231  if (!audio_frame) {
232    std::cerr << "Error: avcodec_alloc_frame for "
233              << in_path.value() << std::endl;
234    return 1;
235  }
236
237  // Buffer used for video decoding.
238  scoped_ptr_malloc<AVFrame, media::ScopedPtrAVFree> video_frame(
239      avcodec_alloc_frame());
240  if (!video_frame) {
241    std::cerr << "Error: avcodec_alloc_frame for "
242              << in_path.value() << std::endl;
243    return 1;
244  }
245
246  // Stats collector.
247  EnterTimingSection();
248  std::vector<double> decode_times;
249  decode_times.reserve(4096);
250  // Parse through the entire stream until we hit EOF.
251#if SHOW_VERBOSE
252  base::TimeTicks start = base::TimeTicks::HighResNow();
253#endif
254  int frames = 0;
255  int read_result = 0;
256  do {
257    read_result = av_read_frame(format_context, &packet);
258
259    if (read_result < 0) {
260      if (max_loops) {
261        --max_loops;
262      }
263      if (max_loops > 0) {
264        av_seek_frame(format_context, -1, 0, AVSEEK_FLAG_BACKWARD);
265        read_result = 0;
266        continue;
267      }
268      if (flush) {
269        packet.stream_index = target_stream;
270        packet.size = 0;
271      } else {
272        break;
273      }
274    }
275
276    // Only decode packets from our target stream.
277    if (packet.stream_index == target_stream) {
278      int result = -1;
279      if (target_codec == AVMEDIA_TYPE_AUDIO) {
280        int size_out = 0;
281        int got_audio = 0;
282
283        avcodec_get_frame_defaults(audio_frame.get());
284
285        base::TimeTicks decode_start = base::TimeTicks::HighResNow();
286        result = avcodec_decode_audio4(codec_context, audio_frame.get(),
287                                       &got_audio, &packet);
288        base::TimeDelta delta = base::TimeTicks::HighResNow() - decode_start;
289
290        if (got_audio) {
291          size_out = av_samples_get_buffer_size(
292              NULL, codec_context->channels, audio_frame->nb_samples,
293              codec_context->sample_fmt, 1);
294        }
295
296        if (got_audio && size_out) {
297          decode_times.push_back(delta.InMillisecondsF());
298          ++frames;
299          read_result = 0;  // Force continuation.
300
301          if (output) {
302            if (fwrite(audio_frame->data[0], 1, size_out, output) !=
303                static_cast<size_t>(size_out)) {
304              std::cerr << "Error: Could not write "
305                        << size_out << " bytes for " << in_path.value()
306                        << std::endl;
307              return 1;
308            }
309          }
310
311          const uint8* u8_samples =
312              reinterpret_cast<const uint8*>(audio_frame->data[0]);
313          if (hash_djb2) {
314            hash_value = DJB2Hash(u8_samples, size_out, hash_value);
315          }
316          if (hash_md5) {
317            base::MD5Update(
318                &ctx,
319                base::StringPiece(reinterpret_cast<const char*>(u8_samples),
320                                                                size_out));
321          }
322        }
323      } else if (target_codec == AVMEDIA_TYPE_VIDEO) {
324        int got_picture = 0;
325
326        avcodec_get_frame_defaults(video_frame.get());
327
328        base::TimeTicks decode_start = base::TimeTicks::HighResNow();
329        result = avcodec_decode_video2(codec_context, video_frame.get(),
330                                       &got_picture, &packet);
331        base::TimeDelta delta = base::TimeTicks::HighResNow() - decode_start;
332
333        if (got_picture) {
334          decode_times.push_back(delta.InMillisecondsF());
335          ++frames;
336          read_result = 0;  // Force continuation.
337
338          for (int plane = 0; plane < 3; ++plane) {
339            const uint8* source = video_frame->data[plane];
340            const size_t source_stride = video_frame->linesize[plane];
341            size_t bytes_per_line = codec_context->width;
342            size_t copy_lines = codec_context->height;
343            if (plane != 0) {
344              switch (codec_context->pix_fmt) {
345                case PIX_FMT_YUV420P:
346                case PIX_FMT_YUVJ420P:
347                  bytes_per_line /= 2;
348                  copy_lines = (copy_lines + 1) / 2;
349                  break;
350                case PIX_FMT_YUV422P:
351                case PIX_FMT_YUVJ422P:
352                  bytes_per_line /= 2;
353                  break;
354                case PIX_FMT_YUV444P:
355                case PIX_FMT_YUVJ444P:
356                  break;
357                default:
358                  std::cerr << "Error: Unknown video format "
359                            << codec_context->pix_fmt;
360                  return 1;
361              }
362            }
363            if (output) {
364              for (size_t i = 0; i < copy_lines; ++i) {
365                if (fwrite(source, 1, bytes_per_line, output) !=
366                           bytes_per_line) {
367                  std::cerr << "Error: Could not write data after "
368                            << copy_lines << " lines for "
369                            << in_path.value() << std::endl;
370                  return 1;
371                }
372                source += source_stride;
373              }
374            }
375            if (hash_djb2) {
376              for (size_t i = 0; i < copy_lines; ++i) {
377                hash_value = DJB2Hash(source, bytes_per_line, hash_value);
378                source += source_stride;
379              }
380            }
381            if (hash_md5) {
382              for (size_t i = 0; i < copy_lines; ++i) {
383                base::MD5Update(
384                    &ctx,
385                    base::StringPiece(reinterpret_cast<const char*>(source),
386                                bytes_per_line));
387                source += source_stride;
388              }
389            }
390          }
391        }
392      } else {
393        NOTREACHED();
394      }
395
396      // Make sure our decoding went OK.
397      if (result < 0) {
398        std::cerr << "Error: avcodec_decode returned "
399                  << result << " for " << in_path.value() << std::endl;
400        return 1;
401      }
402    }
403    // Free our packet.
404    av_free_packet(&packet);
405
406    if (max_frames && (frames >= max_frames))
407      break;
408  } while (read_result >= 0);
409#if SHOW_VERBOSE
410  base::TimeDelta total = base::TimeTicks::HighResNow() - start;
411#endif
412  LeaveTimingSection();
413
414  // Clean up.
415  if (output)
416    file_util::CloseFile(output);
417
418  // Calculate the sum of times.  Note that some of these may be zero.
419  double sum = 0;
420  for (size_t i = 0; i < decode_times.size(); ++i) {
421    sum += decode_times[i];
422  }
423
424  if (sum > 0) {
425    if (target_codec == AVMEDIA_TYPE_AUDIO) {
426      // Calculate the average milliseconds per frame.
427      // Audio decoding is usually in the millisecond or range, and
428      // best expressed in time (ms) rather than FPS, which can approach
429      // infinity.
430      double ms = sum / frames;
431      // Print our results.
432      log_out->setf(std::ios::fixed);
433      log_out->precision(2);
434      *log_out << "TIME PER FRAME (MS):" << std::setw(11) << ms << std::endl;
435    } else if (target_codec == AVMEDIA_TYPE_VIDEO) {
436      // Calculate the average frames per second.
437      // Video decoding is expressed in Frames Per Second - a term easily
438      // understood and should exceed a typical target of 30 fps.
439      double fps = frames * 1000.0 / sum;
440      // Print our results.
441      log_out->setf(std::ios::fixed);
442      log_out->precision(2);
443      *log_out << "FPS:" << std::setw(11) << fps << std::endl;
444    }
445  }
446
447#if SHOW_VERBOSE
448  // Print our results.
449  log_out->setf(std::ios::fixed);
450  log_out->precision(2);
451  *log_out << std::endl;
452  *log_out << "     Frames:" << std::setw(11) << frames
453           << std::endl;
454  *log_out << "      Total:" << std::setw(11) << total.InMillisecondsF()
455           << " ms" << std::endl;
456  *log_out << "  Summation:" << std::setw(11) << sum
457           << " ms" << std::endl;
458
459  if (frames > 0) {
460    // Calculate the average time per frame.
461    double average = sum / frames;
462
463    // Calculate the sum of the squared differences.
464    // Standard deviation will only be accurate if no threads are used.
465    // TODO(fbarchard): Rethink standard deviation calculation.
466    double squared_sum = 0;
467    for (int i = 0; i < frames; ++i) {
468      double difference = decode_times[i] - average;
469      squared_sum += difference * difference;
470    }
471
472    // Calculate the standard deviation (jitter).
473    double stddev = sqrt(squared_sum / frames);
474
475    *log_out << "    Average:" << std::setw(11) << average
476             << " ms" << std::endl;
477    *log_out << "     StdDev:" << std::setw(11) << stddev
478             << " ms" << std::endl;
479  }
480  if (hash_djb2) {
481    *log_out << "  DJB2 Hash:" << std::setw(11) << hash_value
482             << "  " << in_path.value() << std::endl;
483  }
484  if (hash_md5) {
485    base::MD5Digest digest;  // The result of the computation.
486    base::MD5Final(&digest, &ctx);
487    *log_out << "   MD5 Hash: " << base::MD5DigestToBase16(digest)
488             << " " << in_path.value() << std::endl;
489  }
490#endif  // SHOW_VERBOSE
491#if defined(ENABLE_WINDOWS_EXCEPTIONS)
492  } __except(EXCEPTION_EXECUTE_HANDLER) {
493    *log_out << "  Exception:" << std::setw(11) << GetExceptionCode()
494             << " " << in_path.value() << std::endl;
495    return 1;
496  }
497#endif
498  CommandLine::Reset();
499  return 0;
500}
501