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// demuxer_bench is a standalone benchmarking tool for FFmpegDemuxer. It
6// simulates the reading requirements for playback by reading from the stream
7// that has the earliest timestamp.
8
9#include <iostream>
10
11#include "base/at_exit.h"
12#include "base/bind.h"
13#include "base/command_line.h"
14#include "base/logging.h"
15#include "base/message_loop/message_loop.h"
16#include "base/strings/string_number_conversions.h"
17#include "media/base/media.h"
18#include "media/base/media_log.h"
19#include "media/filters/ffmpeg_demuxer.h"
20#include "media/filters/file_data_source.h"
21
22namespace switches {
23const char kEnableBitstreamConverter[] = "enable-bitstream-converter";
24}  // namespace switches
25
26class DemuxerHostImpl : public media::DemuxerHost {
27 public:
28  DemuxerHostImpl() {}
29  virtual ~DemuxerHostImpl() {}
30
31  // DataSourceHost implementation.
32  virtual void SetTotalBytes(int64 total_bytes) OVERRIDE {}
33  virtual void AddBufferedByteRange(int64 start, int64 end) OVERRIDE {}
34  virtual void AddBufferedTimeRange(base::TimeDelta start,
35                                    base::TimeDelta end) OVERRIDE {}
36
37  // DemuxerHost implementation.
38  virtual void SetDuration(base::TimeDelta duration) OVERRIDE {}
39  virtual void OnDemuxerError(media::PipelineStatus error) OVERRIDE {}
40
41 private:
42  DISALLOW_COPY_AND_ASSIGN(DemuxerHostImpl);
43};
44
45void QuitLoopWithStatus(base::MessageLoop* message_loop,
46                        media::PipelineStatus status) {
47  CHECK_EQ(status, media::PIPELINE_OK);
48  message_loop->PostTask(FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
49}
50
51static void NeedKey(const std::string& type, scoped_ptr<uint8[]> init_data,
52             int init_data_size) {
53  LOG(INFO) << "File is encrypted.";
54}
55
56typedef std::vector<media::DemuxerStream* > Streams;
57
58// Simulates playback reading requirements by reading from each stream
59// present in |demuxer| in as-close-to-monotonically-increasing timestamp order.
60class StreamReader {
61 public:
62  StreamReader(media::Demuxer* demuxer, bool enable_bitstream_converter);
63  ~StreamReader();
64
65  // Performs a single step read.
66  void Read();
67
68  // Returns true when all streams have reached end of stream.
69  bool IsDone();
70
71  int number_of_streams() { return streams_.size(); }
72  const Streams& streams() { return streams_; }
73  const std::vector<int>& counts() { return counts_; }
74
75 private:
76  void OnReadDone(base::MessageLoop* message_loop,
77                  bool* end_of_stream,
78                  base::TimeDelta* timestamp,
79                  media::DemuxerStream::Status status,
80                  const scoped_refptr<media::DecoderBuffer>& buffer);
81  int GetNextStreamIndexToRead();
82
83  Streams streams_;
84  std::vector<bool> end_of_stream_;
85  std::vector<base::TimeDelta> last_read_timestamp_;
86  std::vector<int> counts_;
87
88  DISALLOW_COPY_AND_ASSIGN(StreamReader);
89};
90
91StreamReader::StreamReader(media::Demuxer* demuxer,
92                           bool enable_bitstream_converter) {
93  media::DemuxerStream* stream =
94      demuxer->GetStream(media::DemuxerStream::AUDIO);
95  if (stream) {
96    streams_.push_back(stream);
97    end_of_stream_.push_back(false);
98    last_read_timestamp_.push_back(media::kNoTimestamp());
99    counts_.push_back(0);
100  }
101
102  stream = demuxer->GetStream(media::DemuxerStream::VIDEO);
103  if (stream) {
104    streams_.push_back(stream);
105    end_of_stream_.push_back(false);
106    last_read_timestamp_.push_back(media::kNoTimestamp());
107    counts_.push_back(0);
108
109    if (enable_bitstream_converter)
110      stream->EnableBitstreamConverter();
111  }
112}
113
114StreamReader::~StreamReader() {}
115
116void StreamReader::Read() {
117  int index = GetNextStreamIndexToRead();
118  bool end_of_stream = false;
119  base::TimeDelta timestamp;
120
121  streams_[index]->Read(base::Bind(
122      &StreamReader::OnReadDone, base::Unretained(this),
123      base::MessageLoop::current(), &end_of_stream, &timestamp));
124  base::MessageLoop::current()->Run();
125
126  CHECK(end_of_stream || timestamp != media::kNoTimestamp());
127  end_of_stream_[index] = end_of_stream;
128  last_read_timestamp_[index] = timestamp;
129  counts_[index]++;
130}
131
132bool StreamReader::IsDone() {
133  for (size_t i = 0; i < end_of_stream_.size(); ++i) {
134    if (!end_of_stream_[i])
135      return false;
136  }
137  return true;
138}
139
140void StreamReader::OnReadDone(
141    base::MessageLoop* message_loop,
142    bool* end_of_stream,
143    base::TimeDelta* timestamp,
144    media::DemuxerStream::Status status,
145    const scoped_refptr<media::DecoderBuffer>& buffer) {
146  CHECK_EQ(status, media::DemuxerStream::kOk);
147  CHECK(buffer.get());
148  *end_of_stream = buffer->end_of_stream();
149  *timestamp = *end_of_stream ? media::kNoTimestamp() : buffer->timestamp();
150  message_loop->PostTask(FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
151}
152
153int StreamReader::GetNextStreamIndexToRead() {
154  int index = -1;
155  for (int i = 0; i < number_of_streams(); ++i) {
156    // Ignore streams at EOS.
157    if (end_of_stream_[i])
158      continue;
159
160    // Use a stream if it hasn't been read from yet.
161    if (last_read_timestamp_[i] == media::kNoTimestamp())
162      return i;
163
164    if (index < 0 ||
165        last_read_timestamp_[i] < last_read_timestamp_[index]) {
166      index = i;
167    }
168  }
169  CHECK_GE(index, 0) << "Couldn't find a stream to read";
170  return index;
171}
172
173int main(int argc, char** argv) {
174  base::AtExitManager at_exit;
175  media::InitializeMediaLibraryForTesting();
176
177  CommandLine::Init(argc, argv);
178  CommandLine* cmd_line = CommandLine::ForCurrentProcess();
179
180  if (cmd_line->GetArgs().empty()) {
181    std::cerr << "Usage: " << argv[0] << " [file]\n\n"
182              << "Options:\n"
183              << "  --" << switches::kEnableBitstreamConverter
184              << " Enables H.264 Annex B bitstream conversion"
185              << std::endl;
186    return 1;
187  }
188
189  base::MessageLoop message_loop;
190  DemuxerHostImpl demuxer_host;
191  base::FilePath file_path(cmd_line->GetArgs()[0]);
192
193  // Setup.
194  media::FileDataSource data_source;
195  CHECK(data_source.Initialize(file_path));
196
197  media::FFmpegNeedKeyCB need_key_cb = base::Bind(&NeedKey);
198  media::FFmpegDemuxer demuxer(message_loop.message_loop_proxy(),
199                               &data_source,
200                               need_key_cb,
201                               new media::MediaLog());
202
203  demuxer.Initialize(&demuxer_host, base::Bind(
204      &QuitLoopWithStatus, &message_loop));
205  message_loop.Run();
206
207  StreamReader stream_reader(
208      &demuxer, cmd_line->HasSwitch(switches::kEnableBitstreamConverter));
209
210  // Benchmark.
211  base::TimeTicks start = base::TimeTicks::HighResNow();
212  while (!stream_reader.IsDone()) {
213    stream_reader.Read();
214  }
215  base::TimeTicks end = base::TimeTicks::HighResNow();
216
217  // Results.
218  std::cout << "Time: " << (end - start).InMillisecondsF() << " ms\n";
219  for (int i = 0; i < stream_reader.number_of_streams(); ++i) {
220    media::DemuxerStream* stream = stream_reader.streams()[i];
221    std::cout << "Stream #" << i << ": ";
222
223    if (stream->type() == media::DemuxerStream::AUDIO) {
224      std::cout << "audio";
225    } else if (stream->type() == media::DemuxerStream::VIDEO) {
226      std::cout << "video";
227    } else {
228      std::cout << "unknown";
229    }
230
231    std::cout << ", " << stream_reader.counts()[i] << " packets" << std::endl;
232  }
233
234  // Teardown.
235  demuxer.Stop(base::Bind(
236      &QuitLoopWithStatus, &message_loop, media::PIPELINE_OK));
237  message_loop.Run();
238
239  return 0;
240}
241