chromium/chromecast/media/cma/base/multi_demuxer_stream_adapter_unittest.cc

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <vector>

#include "base/functional/bind.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "chromecast/media/api/decoder_buffer_base.h"
#include "chromecast/media/cma/base/balanced_media_task_runner_factory.h"
#include "chromecast/media/cma/base/demuxer_stream_adapter.h"
#include "chromecast/media/cma/base/demuxer_stream_for_test.h"
#include "chromecast/public/media/cast_decoder_buffer.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/decoder_buffer.h"
#include "media/base/demuxer_stream.h"
#include "media/base/video_decoder_config.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace chromecast {
namespace media {

namespace {
// Maximum pts diff between frames
const int kMaxPtsDiffMs = 2000;
}  // namespace

// Test for multiple streams
class MultiDemuxerStreamAdaptersTest : public testing::Test {
 public:
  MultiDemuxerStreamAdaptersTest();

  MultiDemuxerStreamAdaptersTest(const MultiDemuxerStreamAdaptersTest&) =
      delete;
  MultiDemuxerStreamAdaptersTest& operator=(
      const MultiDemuxerStreamAdaptersTest&) = delete;

  ~MultiDemuxerStreamAdaptersTest() override;

  void Start();
  void Run();

 protected:
  void OnTestTimeout();
  void OnNewFrame(CodedFrameProvider* frame_provider,
                  const scoped_refptr<DecoderBufferBase>& buffer,
                  const ::media::AudioDecoderConfig& audio_config,
                  const ::media::VideoDecoderConfig& video_config);

  // Number of expected read frames.
  int total_expected_frames_;

  // Number of frames actually read so far.
  int frame_received_count_;

  // List of expected frame indices with decoder config changes.
  std::list<int> config_idx_;

  std::vector<std::unique_ptr<DemuxerStreamForTest>> demuxer_streams_;

  std::vector<std::unique_ptr<CodedFrameProvider>> coded_frame_providers_;

 private:
  // exit if all of the streams end
  void OnEos();

  // Number of reading-streams
  int running_stream_count_;

  scoped_refptr<BalancedMediaTaskRunnerFactory> media_task_runner_factory_;

  base::OnceClosure quit_closure_;
};

MultiDemuxerStreamAdaptersTest::MultiDemuxerStreamAdaptersTest() {
}

MultiDemuxerStreamAdaptersTest::~MultiDemuxerStreamAdaptersTest() {
}
void MultiDemuxerStreamAdaptersTest::Run() {
  base::RunLoop loop;
  quit_closure_ = loop.QuitWhenIdleClosure();
  loop.Run();
}
void MultiDemuxerStreamAdaptersTest::Start() {
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&MultiDemuxerStreamAdaptersTest::OnTestTimeout,
                     base::Unretained(this)),
      base::Seconds(5));

  media_task_runner_factory_ =
      new BalancedMediaTaskRunnerFactory(base::Milliseconds(kMaxPtsDiffMs));

  coded_frame_providers_.clear();
  frame_received_count_ = 0;

  for (const auto& stream : demuxer_streams_) {
    coded_frame_providers_.push_back(std::make_unique<DemuxerStreamAdapter>(
        base::SingleThreadTaskRunner::GetCurrentDefault(),
        media_task_runner_factory_, stream.get()));
  }
  running_stream_count_ = coded_frame_providers_.size();

  // read each stream
  for (const auto& code_frame_provider : coded_frame_providers_) {
    auto read_cb =
        base::BindOnce(&MultiDemuxerStreamAdaptersTest::OnNewFrame,
                       base::Unretained(this), code_frame_provider.get());

    base::OnceClosure task = base::BindOnce(
        &CodedFrameProvider::Read, base::Unretained(code_frame_provider.get()),
        std::move(read_cb));

    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, std::move(task));
  }
}

void MultiDemuxerStreamAdaptersTest::OnTestTimeout() {
  if (running_stream_count_ != 0) {
    ADD_FAILURE() << "Test timed out";
  }
}

void MultiDemuxerStreamAdaptersTest::OnNewFrame(
    CodedFrameProvider* frame_provider,
    const scoped_refptr<DecoderBufferBase>& buffer,
    const ::media::AudioDecoderConfig& audio_config,
    const ::media::VideoDecoderConfig& video_config) {
  if (buffer->end_of_stream()) {
    OnEos();
    return;
  }

  frame_received_count_++;
  auto read_cb = base::BindOnce(&MultiDemuxerStreamAdaptersTest::OnNewFrame,
                                base::Unretained(this), frame_provider);
  frame_provider->Read(std::move(read_cb));
}

void MultiDemuxerStreamAdaptersTest::OnEos() {
  running_stream_count_--;
  ASSERT_GE(running_stream_count_, 0);
  if (running_stream_count_ == 0) {
    ASSERT_EQ(frame_received_count_, total_expected_frames_);
    std::move(quit_closure_).Run();
  }
}

TEST_F(MultiDemuxerStreamAdaptersTest, EarlyEos) {
  // We have more than one streams here. One of them is much shorter than the
  // others. When the shortest stream reaches EOS, other streams should still
  // run as usually. BalancedTaskRunner should not be blocked.
  int frame_count_short = 2;
  int frame_count_long =
      frame_count_short +
      kMaxPtsDiffMs / DemuxerStreamForTest::kDemuxerStreamForTestFrameDuration +
      100;
  demuxer_streams_.push_back(std::make_unique<DemuxerStreamForTest>(
      frame_count_short, 2, 0, config_idx_));
  demuxer_streams_.push_back(std::make_unique<DemuxerStreamForTest>(
      frame_count_long, 10, 0, config_idx_));

  total_expected_frames_ = frame_count_short + frame_count_long;

  base::test::SingleThreadTaskEnvironment task_environment;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&MultiDemuxerStreamAdaptersTest::Start,
                                base::Unretained(this)));
  Run();
}
}  // namespace media
}  // namespace chromecast