910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "services/audio/input_controller.h"

#include <memory>
#include <string_view>
#include <utility>

#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "input_controller.h"
#include "media/audio/aecdump_recording_manager.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_manager.h"
#include "media/audio/fake_audio_input_stream.h"
#include "media/audio/fake_audio_log_factory.h"
#include "media/audio/fake_audio_manager.h"
#include "media/audio/mock_audio_manager.h"
#include "media/audio/test_audio_thread.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_glitch_info.h"
#include "media/base/audio_processing.h"
#include "media/base/media_switches.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/audio/audio_processor_handler.h"
#include "services/audio/loopback_signal_provider.h"
#include "services/audio/processing_audio_fifo.h"
#include "services/audio/reference_output.h"
#include "services/audio/reference_signal_provider.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::_;
using ::testing::AtLeast;
using ::testing::Exactly;
using ::testing::InvokeWithoutArgs;
using ::testing::NiceMock;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::StrictMock;

namespace audio {

namespace {

const int kSampleRate = media::AudioParameters::kAudioCDSampleRate;
const media::ChannelLayoutConfig kChannelLayoutConfig =
    media::ChannelLayoutConfig::Stereo();
const int kSamplesPerPacket = kSampleRate / 100;

// InputController will poll once every second, so wait at most a bit
// more than that for the callbacks.
constexpr base::TimeDelta kOnMutePollInterval = base::Milliseconds(1000);

using ReferenceOpenOutcome = ReferenceSignalProvider::ReferenceOpenOutcome;

// Struct to hold the parameters for UMA delay tests.
struct DelayUmaTestData {
  ReferenceSignalProvider::Type provider_type;
  const char* expected_uma_name;
};

std::unique_ptr<LoopbackMixin> DoNotCreateLoopbackMixin(
    std::string_view device_id,
    const media::AudioParameters& params,
    LoopbackMixin::OnDataCallback on_data_callback) {
  return nullptr;
}
}  // namespace

class MockInputControllerEventHandler : public InputController::EventHandler {
 public:
  MockInputControllerEventHandler() = default;

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

  void OnLog(std::string_view) override {}

  MOCK_METHOD1(OnCreated, void(bool initially_muted));
  MOCK_METHOD1(OnError, void(InputController::ErrorCode error_code));
  MOCK_METHOD1(OnMuted, void(bool is_muted));
};

class MockSyncWriter : public InputController::SyncWriter {
 public:
  MockSyncWriter() = default;

  MOCK_METHOD4(Write,
               void(const media::AudioBus* data,
                    double volume,
                    base::TimeTicks capture_time,
                    const media::AudioGlitchInfo& audio_glitch_info));
  MOCK_METHOD0(Close, void());
};

class MockAudioInputStream : public media::AudioInputStream {
 public:
  MOCK_METHOD0(Open, OpenOutcome());
  MOCK_METHOD0(Stop, void());
  MOCK_METHOD0(Close, void());
  MOCK_METHOD0(GetMaxVolume, double());
  MOCK_METHOD1(SetVolume, void(double volume));
  MOCK_METHOD0(GetVolume, double());
  MOCK_METHOD1(SetAutomaticGainControl, bool(bool enabled));
  MOCK_METHOD0(GetAutomaticGainControl, bool());
  MOCK_METHOD0(IsMuted, bool());
  MOCK_METHOD1(SetOutputDeviceForAec,
               void(const std::string& output_device_id));

  void Start(AudioInputCallback* callback) override {
    captured_callback_ = callback;
  }

  std::optional<AudioInputCallback*> captured_callback_;
};

class FakeLoopbackSignalProvider : public LoopbackSignalProviderInterface {
 public:
  FakeLoopbackSignalProvider(const media::AudioParameters& params,
                             LoopbackSignalProviderInterface* callback_receiver)
      : params_(params), callback_receiver_(callback_receiver) {}

  ~FakeLoopbackSignalProvider() override = default;

  void Start() override { callback_receiver_->Start(); }

  base::TimeTicks PullLoopbackData(media::AudioBus* audio_bus,
                                   base::TimeTicks capture_time,
                                   double volume) override {
    EXPECT_EQ(audio_bus->frames(), params_.frames_per_buffer());
    EXPECT_EQ(audio_bus->channels(), params_.channels());
    return callback_receiver_->PullLoopbackData(audio_bus, capture_time,
                                                volume);
  }

 private:
  const media::AudioParameters params_;
  raw_ptr<LoopbackSignalProviderInterface> const callback_receiver_;
};

class LoopbackMixinVerifier : public LoopbackSignalProviderInterface {
 public:
  LoopbackMixinVerifier() = default;
  ~LoopbackMixinVerifier() override = default;

  // LoopbackSignalProviderInterface
  MOCK_METHOD(void, Start, (), (override));
  MOCK_METHOD(base::TimeTicks,
              PullLoopbackData,
              (media::AudioBus * audio_bus,
               base::TimeTicks capture_time,
               double volume),
              (override));

  MOCK_METHOD(void, MaybeCreateMixinCalled, (std::string_view device_id));

  std::unique_ptr<LoopbackMixin> MaybeCreateMixin(
      std::string_view device_id,
      const media::AudioParameters& params,
      LoopbackMixin::OnDataCallback on_data_callback) {
    MaybeCreateMixinCalled(device_id);

    if (device_id != media::AudioDeviceDescription::kLoopbackWithoutChromeId) {
      return nullptr;
    }

    return std::make_unique<LoopbackMixinUnderTest>(
        std::make_unique<FakeLoopbackSignalProvider>(params, this), params,
        std::move(on_data_callback));
  }

 private:
  // Test class to access the protected constructor of LoopbackMixin.
  class LoopbackMixinUnderTest : public LoopbackMixin {
   public:
    LoopbackMixinUnderTest(
        std::unique_ptr<LoopbackSignalProviderInterface> signal_provider,
        const media::AudioParameters& params,
        OnDataCallback on_data_callback)
        : LoopbackMixin(std::move(signal_provider),
                        params,
                        std::move(on_data_callback)) {}
  };
};

enum class AudioManagerType { MOCK, FAKE };

template <base::test::TaskEnvironment::TimeSource TimeSource =
              base::test::TaskEnvironment::TimeSource::MOCK_TIME,
          AudioManagerType audio_manager_type = AudioManagerType::FAKE>
class TimeSourceInputControllerTest : public ::testing::Test {
 public:
  TimeSourceInputControllerTest()
      : task_environment_(TimeSource),
        audio_manager_(
            audio_manager_type == AudioManagerType::FAKE
                ? static_cast<std::unique_ptr<media::AudioManager>>(
                      std::make_unique<media::FakeAudioManager>(
                          std::make_unique<media::TestAudioThread>(false),
                          &log_factory_))
                : static_cast<std::unique_ptr<media::AudioManager>>(
                      std::make_unique<media::MockAudioManager>(
                          std::make_unique<media::TestAudioThread>(false)))),
        aecdump_recording_manager_(audio_manager_->GetTaskRunner()),
        params_(media::AudioParameters::AUDIO_FAKE,
                kChannelLayoutConfig,
                kSampleRate,
                kSamplesPerPacket) {}

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

  ~TimeSourceInputControllerTest() override {
    audio_manager_->Shutdown();
    task_environment_.RunUntilIdle();
  }

 protected:
  void CreateAudioControllerWithMixin(const std::string& device_id) {
    EXPECT_CALL(mixin_verifier_, MaybeCreateMixinCalled(device_id));
    controller_ = InputController::Create(
        audio_manager_.get(), &event_handler_, &sync_writer_,
        /*device_output_listener =*/nullptr, &aecdump_recording_manager_,
        /*ml_model_manager=*/nullptr,
        /*processing_config =*/nullptr,
        // base::Unretained is safe: `mixin_verifier_` outlives `controller_`
        base::BindOnce(&LoopbackMixinVerifier::MaybeCreateMixin,
                       base::Unretained(&mixin_verifier_)),
        params_, device_id, false);
  }

  virtual void CreateAudioController() {
    controller_ = InputController::Create(
        audio_manager_.get(), &event_handler_, &sync_writer_,
        /*device_output_listener =*/nullptr, &aecdump_recording_manager_,
        /*ml_model_manager=*/nullptr,
        /*processing_config =*/nullptr,
        base::BindOnce(&DoNotCreateLoopbackMixin), params_,
        media::AudioDeviceDescription::kDefaultDeviceId, false);
  }

  base::test::TaskEnvironment task_environment_;

  StrictMock<LoopbackMixinVerifier> mixin_verifier_;
  std::unique_ptr<media::AudioManager> audio_manager_;
  media::AecdumpRecordingManager aecdump_recording_manager_;
  std::unique_ptr<InputController> controller_;
  media::FakeAudioLogFactory log_factory_;
  MockInputControllerEventHandler event_handler_;
  MockSyncWriter sync_writer_;
  media::AudioParameters params_;
};

using SystemTimeInputControllerTest = TimeSourceInputControllerTest<
    base::test::TaskEnvironment::TimeSource::SYSTEM_TIME>;
using InputControllerTest = TimeSourceInputControllerTest<>;
using InputControllerTestWithMockAudioManager = TimeSourceInputControllerTest<
    base::test::TaskEnvironment::TimeSource::MOCK_TIME,
    AudioManagerType::MOCK>;

TEST_F(InputControllerTest, CreateAndCloseWithoutRecording) {
  EXPECT_CALL(event_handler_, OnCreated(_));
  CreateAudioController();
  task_environment_.RunUntilIdle();
  ASSERT_TRUE(controller_.get());

  EXPECT_CALL(sync_writer_, Close());
  controller_->Close();
}

// Test a normal call sequence of create, record and close.
// Note: Must use system time as MOCK_TIME does not support the threads created
// by the FakeAudioInputStream. The callbacks to sync_writer_.Write() are on
// that thread, and thus we must use SYSTEM_TIME. Also verifies that the
// NoAudioServiceAEC UMA is logged when InputController is created without a
// ReferenceSignalProvider.
TEST_F(SystemTimeInputControllerTest, CreateRecordAndClose) {
  EXPECT_CALL(event_handler_, OnCreated(_));
  CreateAudioController();
  ASSERT_TRUE(controller_.get());

  base::HistogramTester histogram_tester;
  base::RunLoop loop;

  {
    // Wait for Write() to be called ten times.
    testing::InSequence s;
    EXPECT_CALL(sync_writer_, Write(NotNull(), _, _, _)).Times(Exactly(9));
    EXPECT_CALL(sync_writer_, Write(NotNull(), _, _, _))
        .Times(AtLeast(1))
        .WillOnce(InvokeWithoutArgs([&]() { loop.Quit(); }));
  }
  controller_->Record();
  loop.Run();

  testing::Mock::VerifyAndClearExpectations(&sync_writer_);

  EXPECT_CALL(sync_writer_, Close());
  controller_->Close();
  histogram_tester.ExpectTotalCount(
      "Media.Audio.InputController.Delay.NoAudioServiceAEC", 10);

  task_environment_.RunUntilIdle();
}

TEST_F(InputControllerTestWithMockAudioManager, LoopbackMixinIsEngaged) {
  MockAudioInputStream mock_stream;
  static_cast<media::MockAudioManager*>(audio_manager_.get())
      ->SetMakeInputStreamCB(base::BindRepeating(
          [](media::AudioInputStream* stream,
             const media::AudioParameters& params,
             const std::string& device_id) { return stream; },
          &mock_stream));
  auto audio_bus = media::AudioBus::Create(params_);

  CreateAudioControllerWithMixin(
      media::AudioDeviceDescription::kLoopbackWithoutChromeId);
  ASSERT_TRUE(controller_.get());

  EXPECT_CALL(mixin_verifier_, Start());
  controller_->Record();

  ASSERT_TRUE(mock_stream.captured_callback_);
  media::AudioInputStream::AudioInputCallback* callback =
      *mock_stream.captured_callback_;

  // Loopbackmixin::OnData() is called.
  EXPECT_CALL(mixin_verifier_, PullLoopbackData(_, _, _));
  // Loopback passed data back to InputController.
  EXPECT_CALL(sync_writer_, Write(NotNull(), _, _, _));
  callback->OnData(audio_bus.get(), base::TimeTicks(), 1, {});

  EXPECT_CALL(sync_writer_, Close());
  controller_->Close();
}

TEST_F(InputControllerTestWithMockAudioManager, PropagatesGlitchInfo) {
  MockAudioInputStream mock_stream;
  static_cast<media::MockAudioManager*>(audio_manager_.get())
      ->SetMakeInputStreamCB(base::BindRepeating(
          [](media::AudioInputStream* stream,
             const media::AudioParameters& params,
             const std::string& device_id) { return stream; },
          &mock_stream));
  auto audio_bus = media::AudioBus::Create(params_);

  CreateAudioController();
  ASSERT_TRUE(controller_.get());
  controller_->Record();

  ASSERT_TRUE(mock_stream.captured_callback_);
  media::AudioInputStream::AudioInputCallback* callback =
      *mock_stream.captured_callback_;

  for (int i = 0; i < 5; i++) {
    media::AudioGlitchInfo audio_glitch_info{
        .duration = base::Milliseconds(123 + i), .count = 5};
    EXPECT_CALL(sync_writer_, Write(NotNull(), _, _, audio_glitch_info));
    callback->OnData(audio_bus.get(), base::TimeTicks(), 1, audio_glitch_info);
    testing::Mock::VerifyAndClearExpectations(&sync_writer_);
  }

  EXPECT_CALL(sync_writer_, Close());
  controller_->Close();
}

TEST_F(InputControllerTest, RecordTwice) {
  EXPECT_CALL(event_handler_, OnCreated(_));
  CreateAudioController();
  ASSERT_TRUE(controller_.get());

  controller_->Record();
  controller_->Record();

  EXPECT_CALL(sync_writer_, Close());
  controller_->Close();
}

TEST_F(InputControllerTest, CloseTwice) {
  EXPECT_CALL(event_handler_, OnCreated(_));
  CreateAudioController();
  ASSERT_TRUE(controller_.get());

  controller_->Record();

  EXPECT_CALL(sync_writer_, Close());
  controller_->Close();

  controller_->Close();
}

// Test that InputController sends OnMute callbacks properly.
TEST_F(InputControllerTest, TestOnmutedCallbackInitiallyUnmuted) {
  EXPECT_CALL(event_handler_, OnCreated(false));
  EXPECT_CALL(sync_writer_, Close());

  media::FakeAudioInputStream::SetGlobalMutedState(false);
  CreateAudioController();
  ASSERT_TRUE(controller_.get());
  task_environment_.FastForwardBy(kOnMutePollInterval);

  testing::Mock::VerifyAndClearExpectations(&event_handler_);
  EXPECT_CALL(event_handler_, OnMuted(true));
  media::FakeAudioInputStream::SetGlobalMutedState(true);
  task_environment_.FastForwardBy(kOnMutePollInterval);

  testing::Mock::VerifyAndClearExpectations(&event_handler_);
  EXPECT_CALL(event_handler_, OnMuted(false));
  media::FakeAudioInputStream::SetGlobalMutedState(false);
  task_environment_.FastForwardBy(kOnMutePollInterval);

  controller_->Close();
}

TEST_F(InputControllerTest, TestOnmutedCallbackInitiallyMuted) {
  EXPECT_CALL(event_handler_, OnCreated(true));
  EXPECT_CALL(sync_writer_, Close());

  media::FakeAudioInputStream::SetGlobalMutedState(true);
  CreateAudioController();
  ASSERT_TRUE(controller_.get());
  task_environment_.FastForwardBy(kOnMutePollInterval);

  testing::Mock::VerifyAndClearExpectations(&event_handler_);

  EXPECT_CALL(event_handler_, OnMuted(false));
  media::FakeAudioInputStream::SetGlobalMutedState(false);
  task_environment_.FastForwardBy(kOnMutePollInterval);

  controller_->Close();
}

#if BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
class InputControllerTestHelper {
 public:
  explicit InputControllerTestHelper(InputController* controller)
      : controller_(controller) {}

  bool IsUsingProcessingThread() {
    return !!controller_->processing_fifo_.get();
  }

  // Adds a callback that will be run immediately after processing is done, in
  // the same sequence as the processing callback.
  // Should be called before starting the processing thread.
  void AttachOnProcessedCallback(base::RepeatingClosure on_processed_callback) {
    controller_->processing_fifo_->AttachOnProcessedCallbackForTesting(
        std::move(on_processed_callback));
  }

  int FifoSize() {
    CHECK(IsUsingProcessingThread());
    return controller_->processing_fifo_->fifo_size();
  }

  // Simulates the AudioProcessorHandler receiving an error.
  void CallOnReferenceStreamError() {
    // Cast to ReferenceOutput::Listener* to get public access to
    // OnReferenceStreamError.
    static_cast<ReferenceOutput::Listener*>(
        controller_->audio_processor_handler_.get())
        ->OnReferenceStreamError();
  }

 private:
  raw_ptr<InputController> controller_;
};

class MockReferenceSignalProvider : public ReferenceSignalProvider {
 public:
  MockReferenceSignalProvider() = default;
  ~MockReferenceSignalProvider() override = default;

  MOCK_METHOD(Type, GetType, (), (const, override));
  MOCK_METHOD(ReferenceOpenOutcome,
              StartListening,
              (ReferenceOutput::Listener*, const std::string&),
              (override));
  MOCK_METHOD(void, StopListening, (ReferenceOutput::Listener*), (override));
};

template <base::test::TaskEnvironment::TimeSource TimeSource =
              base::test::TaskEnvironment::TimeSource::MOCK_TIME>
class TimeSourceInputControllerTestWithReferenceSignalProvider
    : public TimeSourceInputControllerTest<TimeSource> {
 protected:
  void CreateAudioController() final {
    // Must use |this| to access template base class members:
    // https://stackoverflow.com/q/4643074
    this->controller_ = InputController::Create(
        this->audio_manager_.get(), &this->event_handler_, &this->sync_writer_,
        std::move(reference_signal_provider_unique_),
        &this->aecdump_recording_manager_, /*ml_model_manager=*/nullptr,
        std::move(processing_config_),
        base::BindOnce(&DoNotCreateLoopbackMixin), this->params_,
        media::AudioDeviceDescription::kDefaultDeviceId, false);

    helper_ =
        std::make_unique<InputControllerTestHelper>(this->controller_.get());
  }

  enum class AudioProcessingType {
    // No effects, audio does not need to be modified.
    kNone,
    // Effects that modify audio but do not require a playout reference signal.
    kWithoutPlayoutReference,
    // Effects that require a playout reference signal.
    kWithPlayoutReference
  };

  void SetupProcessingConfig(AudioProcessingType audio_processing_type) {
    media::AudioProcessingSettings settings;
    settings.echo_cancellation = false;
    settings.noise_suppression = false;
    settings.automatic_gain_control = false;
    settings.multi_channel_capture_processing = false;
    switch (audio_processing_type) {
      case AudioProcessingType::kNone:
        break;
      case AudioProcessingType::kWithoutPlayoutReference:
        settings.noise_suppression = true;
        break;
      case AudioProcessingType::kWithPlayoutReference:
        settings.echo_cancellation = true;
        break;
    }
    processing_config_ = media::mojom::AudioProcessingConfig::New(
        remote_controls_.BindNewPipeAndPassReceiver(), settings);
  }

  // Used for testing that a specific OpenOutcome is translated to a specific
  // ErrorCode.
  void TestReferenceOpenError(ReferenceOpenOutcome reference_open_outcome,
                              InputController::ErrorCode expected_error_code);

  // This may or may not be moved into the input controller on creation,
  // depending on if the InputController is going to do echo cancellation.
  std::unique_ptr<NiceMock<MockReferenceSignalProvider>>
      reference_signal_provider_unique_ =
          std::make_unique<NiceMock<MockReferenceSignalProvider>>();
  // The MockReferenceSignalProvider will be destroyed automatically when the
  // InputController is destroyed. We retain a pointer to it to be able to
  // expect mock calls. It will be dangling between the destruction of the
  // InputController and the destruction of the test suite, so we disable
  // dangling pointer detection.
  raw_ptr<NiceMock<MockReferenceSignalProvider>, DisableDanglingPtrDetection>
      reference_signal_provider_ = reference_signal_provider_unique_.get();
  media::mojom::AudioProcessingConfigPtr processing_config_;
  mojo::Remote<media::mojom::AudioProcessorControls> remote_controls_;
  std::unique_ptr<InputControllerTestHelper> helper_;
};

using SystemTimeInputControllerTestWithReferenceSignalProvider =
    TimeSourceInputControllerTestWithReferenceSignalProvider<
        base::test::TaskEnvironment::TimeSource::SYSTEM_TIME>;
using InputControllerTestWithReferenceSignalProvider =
    TimeSourceInputControllerTestWithReferenceSignalProvider<>;

TEST_F(InputControllerTestWithReferenceSignalProvider,
       CreateWithAudioProcessingConfig_WithSomeEffectsEnabled) {
  SetupProcessingConfig(AudioProcessingType::kWithoutPlayoutReference);

  CreateAudioController();

  ASSERT_TRUE(controller_.get());

  base::RunLoop loop;
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
                                                           loop.QuitClosure());
  loop.Run();

  // |controller_| should have bound the pending AudioProcessorControls
  // receiver it received through its ctor.
  EXPECT_TRUE(remote_controls_.is_connected());

  // InputController shouldn't offload processing work when there is no playout
  // reference.
  EXPECT_FALSE(helper_->IsUsingProcessingThread());

  // Test cleanup.
  controller_->Close();
}

TEST_F(InputControllerTestWithReferenceSignalProvider,
       CreateWithAudioProcessingConfig_WithoutEnablingEffects) {
  SetupProcessingConfig(AudioProcessingType::kNone);

  CreateAudioController();

  ASSERT_TRUE(controller_.get());

  base::RunLoop loop;
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
                                                           loop.QuitClosure());
  loop.Run();

  // When all forms of audio processing are disabled, |controller_| should
  // ignore the pending AudioProcessorControls Receiver it received in its
  // ctor.
  EXPECT_FALSE(remote_controls_.is_connected());

  // InputController shouldn't spin up a processing thread if it's not needed.
  EXPECT_FALSE(helper_->IsUsingProcessingThread());

  // Test cleanup.
  controller_->Close();
}

TEST_F(InputControllerTestWithReferenceSignalProvider,
       CreateWithAudioProcessingConfig_VerifyFifoUsage) {
  SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference);

  CreateAudioController();

  ASSERT_TRUE(controller_.get());

  EXPECT_TRUE(helper_->IsUsingProcessingThread());

  // Test cleanup.
  controller_->Close();
}

TEST_F(
    InputControllerTestWithReferenceSignalProvider,
    CreateWithAudioProcessingConfig_DoesNotListenForPlayoutReferenceIfNotRequired) {
  const std::string kOutputDeviceId = "0x123";

  EXPECT_CALL(*reference_signal_provider_, StartListening(_, _)).Times(0);
  EXPECT_CALL(*reference_signal_provider_, StopListening(_)).Times(0);

  SetupProcessingConfig(AudioProcessingType::kWithoutPlayoutReference);
  CreateAudioController();

  ASSERT_TRUE(controller_.get());

  controller_->Record();
  controller_->SetOutputDeviceForAec(kOutputDeviceId);

  // InputController spin up a processing thread if it's not needed.
  EXPECT_FALSE(helper_->IsUsingProcessingThread());

  controller_->Close();

  EXPECT_FALSE(helper_->IsUsingProcessingThread());
}

TEST_F(InputControllerTestWithReferenceSignalProvider,
       RecordBeforeSetOutputForAec) {
  const std::string kOutputDeviceId = "0x123";

  // Calling Record() will start listening to the "" device by default.
  EXPECT_CALL(*reference_signal_provider_, StartListening(_, ""))
      .Times(1)
      .WillOnce(Return(ReferenceOpenOutcome::SUCCESS));
  EXPECT_CALL(*reference_signal_provider_, StartListening(_, kOutputDeviceId))
      .Times(1)
      .WillOnce(Return(ReferenceOpenOutcome::SUCCESS));
  EXPECT_CALL(*reference_signal_provider_, StopListening(_)).Times(1);

  SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference);

  CreateAudioController();

  ASSERT_TRUE(controller_.get());

  controller_->Record();
  controller_->SetOutputDeviceForAec(kOutputDeviceId);

  // InputController should offload processing to its own thread.
  EXPECT_TRUE(helper_->IsUsingProcessingThread());

  controller_->Close();

  // The processing thread should be stopped after controller has closed.
  EXPECT_FALSE(helper_->IsUsingProcessingThread());
}

TEST_F(InputControllerTestWithReferenceSignalProvider,
       RecordAfterSetOutputForAec) {
  const std::string kOutputDeviceId = "0x123";

  EXPECT_CALL(*reference_signal_provider_, StartListening(_, kOutputDeviceId))
      .Times(1)
      .WillOnce(Return(ReferenceOpenOutcome::SUCCESS));
  EXPECT_CALL(*reference_signal_provider_, StopListening(_)).Times(1);

  SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference);
  CreateAudioController();

  ASSERT_TRUE(controller_.get());

  controller_->SetOutputDeviceForAec(kOutputDeviceId);
  controller_->Record();

  // InputController should offload processing to its own thread.
  EXPECT_TRUE(helper_->IsUsingProcessingThread());

  controller_->Close();

  // The processing thread should be stopped after controller has closed.
  EXPECT_FALSE(helper_->IsUsingProcessingThread());
}

TEST_F(InputControllerTestWithReferenceSignalProvider, FifoSize) {
  const std::string kOutputDeviceId = "0x123";
  EXPECT_CALL(*reference_signal_provider_, StartListening(_, kOutputDeviceId))
      .Times(1)
      .WillOnce(Return(ReferenceOpenOutcome::SUCCESS));
  EXPECT_CALL(*reference_signal_provider_, StopListening(_)).Times(1);

  SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference);
  CreateAudioController();

  ASSERT_TRUE(controller_.get());

  controller_->SetOutputDeviceForAec(kOutputDeviceId);
  controller_->Record();

  EXPECT_TRUE(helper_->IsUsingProcessingThread());
  EXPECT_EQ(helper_->FifoSize(), InputController::kProcessingFifoSize);

  // InputController should offload processing to its own thread.
  EXPECT_TRUE(helper_->IsUsingProcessingThread());

  controller_->Close();
  EXPECT_FALSE(helper_->IsUsingProcessingThread());
}

TEST_F(InputControllerTestWithReferenceSignalProvider, ChangeOutputForAec) {
  const std::string kOutputDeviceId = "0x123";
  const std::string kOtherOutputDeviceId = "0x987";

  // Each output ID should receive one call to StartListening().
  EXPECT_CALL(*reference_signal_provider_, StartListening(_, kOutputDeviceId))
      .Times(1)
      .WillOnce(Return(ReferenceOpenOutcome::SUCCESS));
  EXPECT_CALL(*reference_signal_provider_,
              StartListening(_, kOtherOutputDeviceId))
      .Times(1)
      .WillOnce(Return(ReferenceOpenOutcome::SUCCESS));

  // StopListening() should be called once, regardless of how many ID changes.
  EXPECT_CALL(*reference_signal_provider_, StopListening(_)).Times(1);

  SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference);
  CreateAudioController();

  ASSERT_TRUE(controller_.get());

  controller_->SetOutputDeviceForAec(kOutputDeviceId);
  controller_->Record();
  controller_->SetOutputDeviceForAec(kOtherOutputDeviceId);
  controller_->Close();
}

// Test a normal call sequence of create, record and close when audio processing
// is enabled.
// Note: Must use system time as MOCK_TIME does not support the threads created
// by the FakeAudioInputStream. The callbacks to sync_writer_.Write() are on
// that thread, and thus we must use SYSTEM_TIME.
TEST_F(SystemTimeInputControllerTestWithReferenceSignalProvider,
       CreateRecordAndClose) {
  EXPECT_CALL(event_handler_, OnCreated(_));
  SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference);
  CreateAudioController();

  bool data_processed_by_fifo = false;

  // Test that the fifo is enabled.
  auto main_sequence = base::SequencedTaskRunner::GetCurrentDefault();
  auto verify_data_processed = [&data_processed_by_fifo, main_sequence]() {
    // Data should be processed on its own thread.
    EXPECT_FALSE(main_sequence->RunsTasksInCurrentSequence());

    data_processed_by_fifo = true;
  };

  helper_->AttachOnProcessedCallback(
      base::BindLambdaForTesting(verify_data_processed));

  ASSERT_TRUE(controller_.get());

  base::RunLoop loop;

  {
    // Wait for Write() to be called ten times.
    testing::InSequence s;
    EXPECT_CALL(sync_writer_, Write(NotNull(), _, _, _)).Times(Exactly(9));
    EXPECT_CALL(sync_writer_, Write(NotNull(), _, _, _))
        .Times(AtLeast(1))
        .WillOnce(InvokeWithoutArgs([&]() { loop.Quit(); }));
  }
  controller_->Record();

  // InputController should offload processing to its own thread if the
  // processing FIFO is enabled.
  EXPECT_TRUE(helper_->IsUsingProcessingThread());

  loop.Run();

  testing::Mock::VerifyAndClearExpectations(&sync_writer_);

  EXPECT_CALL(sync_writer_, Close());
  controller_->Close();

  // The processing thread should be stopped after controller has closed.
  EXPECT_FALSE(helper_->IsUsingProcessingThread());

  task_environment_.RunUntilIdle();

  EXPECT_TRUE(data_processed_by_fifo);
}

TEST_F(InputControllerTestWithReferenceSignalProvider, ReferenceStreamError) {
  const std::string kOutputDeviceId = "0x123";
  EXPECT_CALL(*reference_signal_provider_, StartListening(_, kOutputDeviceId))
      .Times(1)
      .WillOnce(Return(ReferenceOpenOutcome::SUCCESS));
  EXPECT_CALL(*reference_signal_provider_, StopListening(_)).Times(1);

  SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference);
  CreateAudioController();

  ASSERT_TRUE(controller_.get());

  controller_->SetOutputDeviceForAec(kOutputDeviceId);
  controller_->Record();
  EXPECT_TRUE(helper_->IsUsingProcessingThread());

  // Sending a ReferenceStreamError should result in an error being sent to the
  // EventHandler.
  EXPECT_CALL(event_handler_, OnError(InputController::REFERENCE_STREAM_ERROR));
  helper_->CallOnReferenceStreamError();

  controller_->Close();
}

class ParameterizedInputControllerUmaDelayTest
    : public SystemTimeInputControllerTestWithReferenceSignalProvider,
      public ::testing::WithParamInterface<DelayUmaTestData> {};

// Test a normal call sequence of create, record and close when audio processing
// is enabled but also verify that capture delays are recorded correctly using
// two different UMA names where the name depends on the type returned by the
// ReferenceSignalProvider.
// Based on
// SystemTimeInputControllerTestWithReferenceSignalProvider.CreateRecordAndClose.
TEST_P(ParameterizedInputControllerUmaDelayTest, CreateRecordAndClose) {
  const DelayUmaTestData& param = GetParam();

  EXPECT_CALL(event_handler_, OnCreated(_));
  // Use the provider_type from the parameter.
  EXPECT_CALL(*reference_signal_provider_, GetType())
      .WillOnce(testing::Return(param.provider_type));
  EXPECT_CALL(*reference_signal_provider_, StartListening(_, _)).Times(1);
  EXPECT_CALL(*reference_signal_provider_, StopListening(_)).Times(1);
  SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference);
  CreateAudioController();

  bool data_processed_by_fifo = false;

  // Test that the fifo is enabled.
  auto main_sequence = base::SequencedTaskRunner::GetCurrentDefault();
  auto verify_data_processed = [&data_processed_by_fifo, main_sequence]() {
    // Data should be processed on its own thread.
    EXPECT_FALSE(main_sequence->RunsTasksInCurrentSequence());

    data_processed_by_fifo = true;
  };

  helper_->AttachOnProcessedCallback(
      base::BindLambdaForTesting(verify_data_processed));

  ASSERT_TRUE(controller_.get());

  base::HistogramTester histogram_tester;
  base::RunLoop loop;

  {
    // Wait for Write() to be called ten times.
    testing::InSequence s;
    EXPECT_CALL(sync_writer_, Write(NotNull(), _, _, _)).Times(Exactly(9));
    EXPECT_CALL(sync_writer_, Write(NotNull(), _, _, _))
        .Times(AtLeast(1))
        .WillOnce(InvokeWithoutArgs([&]() { loop.Quit(); }));
  }
  controller_->Record();

  // InputController should offload processing to its own thread if the
  // processing FIFO is enabled.
  EXPECT_TRUE(helper_->IsUsingProcessingThread());

  loop.Run();

  testing::Mock::VerifyAndClearExpectations(&sync_writer_);

  EXPECT_CALL(sync_writer_, Close());
  controller_->Close();

  // Use the expected_uma_name from the parameter.
  histogram_tester.ExpectTotalCount(param.expected_uma_name, 10);

  // The processing thread should be stopped after controller has closed.
  EXPECT_FALSE(helper_->IsUsingProcessingThread());

  EXPECT_TRUE(data_processed_by_fifo);
}

// Instantiate the UMA test suite with the two scenarios.
INSTANTIATE_TEST_SUITE_P(
    AECTypeDelayUMAs,
    ParameterizedInputControllerUmaDelayTest,
    ::testing::Values(
        DelayUmaTestData{ReferenceSignalProvider::Type::kOutputDeviceMixer,
                         "Media.Audio.InputController.Delay.ChromeWideAEC"},
        DelayUmaTestData{ReferenceSignalProvider::Type::kLoopbackReference,
                         "Media.Audio.InputController.Delay.LoopbackAEC"}),
    // Provide a human-readable name for each test instance.
    [](const testing::TestParamInfo<
        ParameterizedInputControllerUmaDelayTest::ParamType>& info) {
      switch (info.param.provider_type) {
        case ReferenceSignalProvider::Type::kOutputDeviceMixer:
          return "ChromeWideAEC";
        case ReferenceSignalProvider::Type::kLoopbackReference:
          return "LoopbackAEC";
        default:
          return "UnknownAEC";
      }
    });

template <>
void InputControllerTestWithReferenceSignalProvider::TestReferenceOpenError(
    ReferenceOpenOutcome reference_open_outcome,
    InputController::ErrorCode expected_error_code) {
  const std::string kOutputDeviceId = "0x123";
  // Make StartListening return an error.
  EXPECT_CALL(*reference_signal_provider_, StartListening(_, kOutputDeviceId))
      .Times(1)
      .WillOnce(Return(reference_open_outcome));
  EXPECT_CALL(*reference_signal_provider_, StopListening(_)).Times(1);

  SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference);
  CreateAudioController();

  ASSERT_TRUE(controller_.get());

  controller_->SetOutputDeviceForAec(kOutputDeviceId);

  // Since StartListening will fail with an error, we should get an error on
  // Record().
  EXPECT_CALL(event_handler_, OnError(expected_error_code));
  controller_->Record();
  controller_->Close();
}

TEST_F(InputControllerTestWithReferenceSignalProvider,
       ReferenceStreamOpenError) {
  TestReferenceOpenError(ReferenceOpenOutcome::STREAM_OPEN_ERROR,
                         InputController::REFERENCE_STREAM_OPEN_ERROR);
}

TEST_F(InputControllerTestWithReferenceSignalProvider,
       ReferenceStreamPreviousError) {
  TestReferenceOpenError(ReferenceOpenOutcome::STREAM_PREVIOUS_ERROR,
                         InputController::REFERENCE_STREAM_ERROR);
}

TEST_F(InputControllerTestWithReferenceSignalProvider,
       ReferenceStreamCreateError) {
  TestReferenceOpenError(ReferenceOpenOutcome::STREAM_CREATE_ERROR,
                         InputController::REFERENCE_STREAM_CREATE_ERROR);
}

TEST_F(InputControllerTestWithReferenceSignalProvider,
       ReferenceStreamOpenDeviceInUseError) {
  TestReferenceOpenError(
      ReferenceOpenOutcome::STREAM_OPEN_DEVICE_IN_USE_ERROR,
      InputController::REFERENCE_STREAM_OPEN_DEVICE_IN_USE_ERROR);
}

TEST_F(InputControllerTestWithReferenceSignalProvider,
       ReferenceStreamOpenSystemPermissionsError) {
  TestReferenceOpenError(
      ReferenceOpenOutcome::STREAM_OPEN_SYSTEM_PERMISSIONS_ERROR,
      InputController::REFERENCE_STREAM_OPEN_SYSTEM_PERMISSIONS_ERROR);
}

TEST_F(InputControllerTestWithReferenceSignalProvider,
       CreateWithoutProcessingConfig_DoesNotUseFifo) {
  // This test simulates disabling ChromeWideEchoCancellation, in which case
  // both the AudioProcessingConfig and the ReferenceSignalProvider are null.

  // Destroy the ReferenceSignalProvider before moving it into the
  // InputController.
  reference_signal_provider_unique_.reset();
  // Additionally, we intentionally do not call SetupProcessingConfig(), leaving
  // the AudioProcessingConfig as null.
  CreateAudioController();

  ASSERT_TRUE(controller_.get());
  controller_->Record();

  // We are not doing echo cancellation, so we are not using the fifo.
  EXPECT_FALSE(helper_->IsUsingProcessingThread());

  // Test cleanup.
  controller_->Close();
}

#endif  // BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)

}  // namespace audio