1// Copyright 2013 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#include "base/at_exit.h"
6#include "base/bind.h"
7#include "base/message_loop/message_loop.h"
8#include "base/strings/string_number_conversions.h"
9#include "base/time/time.h"
10#include "media/base/media.h"
11#include "media/base/media_log.h"
12#include "media/base/test_data_util.h"
13#include "media/filters/ffmpeg_demuxer.h"
14#include "media/filters/file_data_source.h"
15#include "testing/gtest/include/gtest/gtest.h"
16#include "testing/perf/perf_test.h"
17
18namespace media {
19
20static const int kBenchmarkIterations = 500;
21
22class DemuxerHostImpl : public media::DemuxerHost {
23 public:
24  DemuxerHostImpl() {}
25  virtual ~DemuxerHostImpl() {}
26
27  // DataSourceHost implementation.
28  virtual void SetTotalBytes(int64 total_bytes) OVERRIDE {}
29  virtual void AddBufferedByteRange(int64 start, int64 end) OVERRIDE {}
30  virtual void AddBufferedTimeRange(base::TimeDelta start,
31                                    base::TimeDelta end) OVERRIDE {}
32
33  // DemuxerHost implementation.
34  virtual void SetDuration(base::TimeDelta duration) OVERRIDE {}
35  virtual void OnDemuxerError(media::PipelineStatus error) OVERRIDE {}
36  virtual void AddTextStream(media::DemuxerStream* text_stream,
37                             const media::TextTrackConfig& config) OVERRIDE {}
38  virtual void RemoveTextStream(media::DemuxerStream* text_stream) OVERRIDE {}
39
40 private:
41  DISALLOW_COPY_AND_ASSIGN(DemuxerHostImpl);
42};
43
44static void QuitLoopWithStatus(base::MessageLoop* message_loop,
45                        media::PipelineStatus status) {
46  CHECK_EQ(status, media::PIPELINE_OK);
47  message_loop->PostTask(FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
48}
49
50static void NeedKey(const std::string& type,
51                    const std::vector<uint8>& init_data) {
52  VLOG(0) << "File is encrypted.";
53}
54
55typedef std::vector<media::DemuxerStream* > Streams;
56
57// Simulates playback reading requirements by reading from each stream
58// present in |demuxer| in as-close-to-monotonically-increasing timestamp order.
59class StreamReader {
60 public:
61  StreamReader(media::Demuxer* demuxer, bool enable_bitstream_converter);
62  ~StreamReader();
63
64  // Performs a single step read.
65  void Read();
66
67  // Returns true when all streams have reached end of stream.
68  bool IsDone();
69
70  int number_of_streams() { return static_cast<int>(streams_.size()); }
71  const Streams& streams() { return streams_; }
72  const std::vector<int>& counts() { return counts_; }
73
74 private:
75  void OnReadDone(base::MessageLoop* message_loop,
76                  bool* end_of_stream,
77                  base::TimeDelta* timestamp,
78                  media::DemuxerStream::Status status,
79                  const scoped_refptr<media::DecoderBuffer>& buffer);
80  int GetNextStreamIndexToRead();
81
82  Streams streams_;
83  std::vector<bool> end_of_stream_;
84  std::vector<base::TimeDelta> last_read_timestamp_;
85  std::vector<int> counts_;
86
87  DISALLOW_COPY_AND_ASSIGN(StreamReader);
88};
89
90StreamReader::StreamReader(media::Demuxer* demuxer,
91                           bool enable_bitstream_converter) {
92  media::DemuxerStream* stream =
93      demuxer->GetStream(media::DemuxerStream::AUDIO);
94  if (stream) {
95    streams_.push_back(stream);
96    end_of_stream_.push_back(false);
97    last_read_timestamp_.push_back(media::kNoTimestamp());
98    counts_.push_back(0);
99  }
100
101  stream = demuxer->GetStream(media::DemuxerStream::VIDEO);
102  if (stream) {
103    streams_.push_back(stream);
104    end_of_stream_.push_back(false);
105    last_read_timestamp_.push_back(media::kNoTimestamp());
106    counts_.push_back(0);
107
108    if (enable_bitstream_converter)
109      stream->EnableBitstreamConverter();
110  }
111}
112
113StreamReader::~StreamReader() {}
114
115void StreamReader::Read() {
116  int index = GetNextStreamIndexToRead();
117  bool end_of_stream = false;
118  base::TimeDelta timestamp;
119
120  streams_[index]->Read(base::Bind(
121      &StreamReader::OnReadDone, base::Unretained(this),
122      base::MessageLoop::current(), &end_of_stream, &timestamp));
123  base::MessageLoop::current()->Run();
124
125  CHECK(end_of_stream || timestamp != media::kNoTimestamp());
126  end_of_stream_[index] = end_of_stream;
127  last_read_timestamp_[index] = timestamp;
128  counts_[index]++;
129}
130
131bool StreamReader::IsDone() {
132  for (size_t i = 0; i < end_of_stream_.size(); ++i) {
133    if (!end_of_stream_[i])
134      return false;
135  }
136  return true;
137}
138
139void StreamReader::OnReadDone(
140    base::MessageLoop* message_loop,
141    bool* end_of_stream,
142    base::TimeDelta* timestamp,
143    media::DemuxerStream::Status status,
144    const scoped_refptr<media::DecoderBuffer>& buffer) {
145  CHECK_EQ(status, media::DemuxerStream::kOk);
146  CHECK(buffer.get());
147  *end_of_stream = buffer->end_of_stream();
148  *timestamp = *end_of_stream ? media::kNoTimestamp() : buffer->timestamp();
149  message_loop->PostTask(FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
150}
151
152int StreamReader::GetNextStreamIndexToRead() {
153  int index = -1;
154  for (int i = 0; i < number_of_streams(); ++i) {
155    // Ignore streams at EOS.
156    if (end_of_stream_[i])
157      continue;
158
159    // Use a stream if it hasn't been read from yet.
160    if (last_read_timestamp_[i] == media::kNoTimestamp())
161      return i;
162
163    if (index < 0 ||
164        last_read_timestamp_[i] < last_read_timestamp_[index]) {
165      index = i;
166    }
167  }
168  CHECK_GE(index, 0) << "Couldn't find a stream to read";
169  return index;
170}
171
172static void RunDemuxerBenchmark(const std::string& filename) {
173  base::FilePath file_path(GetTestDataFilePath(filename));
174  double total_time = 0.0;
175  for (int i = 0; i < kBenchmarkIterations; ++i) {
176    // Setup.
177    base::MessageLoop message_loop;
178    DemuxerHostImpl demuxer_host;
179    FileDataSource data_source;
180    ASSERT_TRUE(data_source.Initialize(file_path));
181
182    Demuxer::NeedKeyCB need_key_cb = base::Bind(&NeedKey);
183    FFmpegDemuxer demuxer(message_loop.message_loop_proxy(),
184                          &data_source,
185                          need_key_cb,
186                          new MediaLog());
187
188    demuxer.Initialize(&demuxer_host,
189                       base::Bind(&QuitLoopWithStatus, &message_loop),
190                       false);
191    message_loop.Run();
192    StreamReader stream_reader(&demuxer, false);
193
194    // Benchmark.
195    base::TimeTicks start = base::TimeTicks::HighResNow();
196    while (!stream_reader.IsDone()) {
197      stream_reader.Read();
198    }
199    base::TimeTicks end = base::TimeTicks::HighResNow();
200    total_time += (end - start).InSecondsF();
201    demuxer.Stop(base::Bind(
202        &QuitLoopWithStatus, &message_loop, PIPELINE_OK));
203    message_loop.Run();
204  }
205
206  perf_test::PrintResult("demuxer_bench",
207                         "",
208                         filename,
209                         kBenchmarkIterations / total_time,
210                         "runs/s",
211                         true);
212}
213
214TEST(DemuxerPerfTest, Demuxer) {
215  RunDemuxerBenchmark("bear.ogv");
216  RunDemuxerBenchmark("bear-640x360.webm");
217  RunDemuxerBenchmark("sfx_s16le.wav");
218#if defined(USE_PROPRIETARY_CODECS)
219  RunDemuxerBenchmark("bear-1280x720.mp4");
220  RunDemuxerBenchmark("sfx.mp3");
221#endif
222#if defined(OS_CHROMEOS)
223  RunDemuxerBenchmark("bear.flac");
224#endif
225#if defined(USE_PROPRIETARY_CODECS) && defined(OS_CHROMEOS)
226  RunDemuxerBenchmark("bear.avi");
227#endif
228}
229
230}  // namespace media
231