chromium/chromecast/media/cma/backend/mixer/stream_mixer_external_audio_pipeline_unittest.cc

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

#include <algorithm>
#include <memory>
#include <vector>

#include "base/location.h"
#include "base/message_loop/message_pump_type.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "chromecast/media/audio/fake_external_audio_pipeline_support.h"
#include "chromecast/media/audio/mixer_service/loopback_connection.h"
#include "chromecast/media/audio/mixer_service/mixer_socket.h"
#include "chromecast/media/cma/backend/mixer/loopback_test_support.h"
#include "chromecast/media/cma/backend/mixer/mock_mixer_source.h"
#include "chromecast/media/cma/backend/mixer/mock_post_processor_factory.h"
#include "chromecast/media/cma/backend/mixer/stream_mixer.h"
#include "chromecast/public/media/external_audio_pipeline_shlib.h"
#include "chromecast/public/media/mixer_output_stream.h"
#include "media/base/audio_bus.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace chromecast {
namespace media {
namespace {

using ::testing::_;
using ::testing::AtLeast;

// Mock for saving and checking loopback audio data.
class MockLoopbackAudioObserver
    : public mixer_service::LoopbackConnection::Delegate {
 public:
  MockLoopbackAudioObserver() {
    ON_CALL(*this, OnLoopbackAudio(_, _, _, _, _, _))
        .WillByDefault(::testing::Invoke(
            this, &MockLoopbackAudioObserver::OnLoopbackAudioImpl));
  }

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

  MOCK_METHOD6(OnLoopbackAudio,
               void(int64_t timestamp,
                    SampleFormat sample_format,
                    int sample_rate,
                    int num_channels,
                    uint8_t* data,
                    int length));
  MOCK_METHOD1(OnLoopbackInterrupted, void(LoopbackInterruptReason));

  const std::vector<float>& data() const { return data_; }

 private:
  void OnLoopbackAudioImpl(int64_t timestamp,
                           SampleFormat sample_format,
                           int sample_rate,
                           int num_channels,
                           uint8_t* data,
                           int length) {
    data_.clear();
    // Save received data to local.
    float* float_data = reinterpret_cast<float*>(const_cast<uint8_t*>(data));
    const size_t size = length / sizeof(float);
    data_.insert(data_.end(), float_data, float_data + size);
  }

  std::vector<float> data_;
};

class ExternalAudioPipelineTest : public ::testing::Test {
 public:
  ExternalAudioPipelineTest()
      : external_audio_pipeline_support_(
            testing::GetFakeExternalAudioPipelineSupport()) {}

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

  void SetUp() override {
    // Set that external library is supported.
    external_audio_pipeline_support_->SetSupported();

    mixer_ = std::make_unique<StreamMixer>(
        nullptr, base::SingleThreadTaskRunner::GetCurrentDefault(), "{}");
  }

  void TearDown() override {
    // Reset library internal state to use it for other unit tests.
    external_audio_pipeline_support_->Reset();
  }
  // Run async operations in the stream mixer.
  void RunLoopForMixer() {
    base::RunLoop run_loop;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, run_loop.QuitClosure());
    run_loop.Run();
  }

  std::unique_ptr<mixer_service::LoopbackConnection> AddLoopbackObserver(
      mixer_service::LoopbackConnection::Delegate* observer) {
    std::unique_ptr<mixer_service::MixerSocket> loopback_socket =
        CreateLoopbackConnectionForTest(mixer_->GetLoopbackHandlerForTest());
    auto connection = std::make_unique<mixer_service::LoopbackConnection>(
        observer, std::move(loopback_socket));
    connection->Connect();
    return connection;
  }

 protected:
  std::unique_ptr<StreamMixer> mixer_;
  testing::FakeExternalAudioPipelineSupport* const
      external_audio_pipeline_support_;

  base::test::SingleThreadTaskEnvironment task_environment_{
      base::test::TaskEnvironment::MainThreadType::IO};
};

// Check that |expected| matches |actual| exactly.
void CompareAudioData(const ::media::AudioBus& expected,
                      const ::media::AudioBus& actual) {
  ASSERT_EQ(expected.channels(), actual.channels());
  ASSERT_EQ(expected.frames(), actual.frames());
  for (int c = 0; c < expected.channels(); ++c) {
    const float* expected_data = expected.channel(c);
    const float* actual_data = actual.channel(c);
    for (int f = 0; f < expected.frames(); ++f) {
      EXPECT_FLOAT_EQ(*expected_data++, *actual_data++) << c << " " << f;
    }
  }
}

// Unit tests for ExternalAudioPipelineShlib library.
// Test media volume notification.
TEST_F(ExternalAudioPipelineTest, SetMediaVolume) {
  ASSERT_EQ(external_audio_pipeline_support_->GetVolume(), 0.0f);

  mixer_->SetVolume(AudioContentType::kMedia, 0.02);
  RunLoopForMixer();
  ASSERT_EQ(external_audio_pipeline_support_->GetVolume(), 0.02f);
}
// Test media muted notification.
TEST_F(ExternalAudioPipelineTest, SetMediaMuted) {
  ASSERT_EQ(external_audio_pipeline_support_->IsMuted(), false);

  mixer_->SetMuted(AudioContentType::kMedia, true);
  RunLoopForMixer();
  ASSERT_EQ(external_audio_pipeline_support_->IsMuted(), true);
}
// Set media volume from library, check notification.
TEST_F(ExternalAudioPipelineTest, SetVolumeChangeRequest) {
  ASSERT_EQ(external_audio_pipeline_support_->GetVolume(), 0.0f);

  external_audio_pipeline_support_->OnVolumeChangeRequest(0.03);
  RunLoopForMixer();
  ASSERT_EQ(external_audio_pipeline_support_->GetVolume(), 0.03f);
}
// Set media mute from library, check notification.
TEST_F(ExternalAudioPipelineTest, SetMuteChangeRequest) {
  ASSERT_EQ(external_audio_pipeline_support_->IsMuted(), false);

  external_audio_pipeline_support_->OnMuteChangeRequest(true);
  RunLoopForMixer();
  ASSERT_EQ(external_audio_pipeline_support_->IsMuted(), true);
}
// Check external library loopback data. Check that passed input to StreamMixer
// comes to CastMediaShlib::LoopbackAudioObserver w/o changes.
TEST_F(ExternalAudioPipelineTest, ExternalAudioPipelineLoopbackData) {
  // Set Volume to 1, because we'd like the input to be w/o changes.
  mixer_->SetVolume(AudioContentType::kMedia, 1);

  // Add fake postprocessor to override test configuration running on device.
  const int kNumChannels = 2;
  mixer_->SetNumOutputChannelsForTest(kNumChannels);
  mixer_->ResetPostProcessorsForTest(
      std::make_unique<MockPostProcessorFactory>(), "{}");

  // Input.
  MockMixerSource input(48000);
  EXPECT_CALL(input, InitializeAudioPlayback(_, _)).Times(1);
  EXPECT_CALL(input, FinalizeAudioPlayback()).Times(1);
  EXPECT_CALL(input, FillAudioPlaybackFrames(_, _, _)).Times(AtLeast(1));

  // Prepare data for test.
  const size_t kSampleSize = 64;
  uint8_t test_data[kSampleSize];
  for (size_t i = 0; i < kSampleSize; ++i)
    test_data[i] = i;

  // Set test data in AudioBus.
  const auto kNumFrames = kSampleSize / kNumChannels;
  auto data = ::media::AudioBus::Create(kNumChannels, kNumFrames);
  data->FromInterleaved<::media::UnsignedInt8SampleTypeTraits>(test_data,
                                                               kNumFrames);
  // Prepare data for compare.
  auto expected = ::media::AudioBus::Create(kNumChannels, kNumFrames);
  data->CopyTo(expected.get());

  mixer_->AddInput(&input);
  MockLoopbackAudioObserver mock_loopback_observer;
  auto connection = AddLoopbackObserver(&mock_loopback_observer);
  EXPECT_CALL(mock_loopback_observer, OnLoopbackAudio(_, _, _, _, _, _))
      .Times(AtLeast(1));
  RunLoopForMixer();

  // Send data to the stream mixer.
  input.SetData(std::move(data));
  RunLoopForMixer();
  RunLoopForMixer();
  RunLoopForMixer();

  // Get actual data from our mocked loopback observer.
  ASSERT_GE(mock_loopback_observer.data().size(), kNumFrames);

  auto actual = ::media::AudioBus::Create(kNumChannels, kNumFrames);
  using FloatType = ::media::Float32SampleTypeTraits;
  actual->FromInterleaved<FloatType>(mock_loopback_observer.data().data(),
                                     kNumFrames);

  CompareAudioData(*expected, *actual);

  // Check OnRemoved.
  mixer_->RemoveInput(&input);

  RunLoopForMixer();
  task_environment_.RunUntilIdle();
}

}  // namespace
}  // namespace media
}  // namespace chromecast