chromium/third_party/blink/renderer/modules/mediastream/processed_local_audio_source_test.cc

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

#include "third_party/blink/renderer/modules/mediastream/processed_local_audio_source.h"

#include <memory>
#include <string>

#include "base/functional/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_glitch_info.h"
#include "media/base/audio_parameters.h"
#include "media/base/audio_timestamp_helper.h"
#include "media/base/media_switches.h"
#include "media/media_buildflags.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/modules/mediastream/web_media_stream_audio_sink.h"
#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
#include "third_party/blink/public/web/web_heap.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/modules/mediastream/media_constraints.h"
#include "third_party/blink/renderer/modules/mediastream/testing_platform_support_with_mock_audio_capture_source.h"
#include "third_party/blink/renderer/modules/webrtc/webrtc_audio_device_impl.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_audio_track.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_component_impl.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_source.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_track_platform.h"

_;
AtLeast;
Invoke;
WithArg;

namespace blink {

namespace {

// Audio parameters for the VerifyAudioFlowWithoutAudioProcessing test.
constexpr int kSampleRate =;
constexpr media::ChannelLayout kChannelLayout =;
constexpr int kDeviceBufferSize =;

enum class ProcessingLocation {};

std::tuple<int, int> ComputeExpectedSourceAndOutputBufferSizes(
    ProcessingLocation processing_location) {}

class FormatCheckingMockAudioSink : public WebMediaStreamAudioSink {};

}  // namespace

class ProcessedLocalAudioSourceBase : public SimTest {};

class ProcessedLocalAudioSourceTest
    : public ProcessedLocalAudioSourceBase,
      public testing::WithParamInterface<ProcessingLocation> {};

// Tests a basic end-to-end start-up, track+sink connections, audio flow, and
// shut-down. The tests in media_stream_audio_test.cc provide more comprehensive
// testing of the object graph connections and multi-threading concerns.
TEST_P(ProcessedLocalAudioSourceTest, VerifyAudioFlowWithoutAudioProcessing) {}

#if BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
INSTANTIATE_TEST_SUITE_P();
#else
INSTANTIATE_TEST_SUITE_P(
    All,
    ProcessedLocalAudioSourceTest,
    testing::Values(ProcessingLocation::kProcessedLocalAudioSource));
#endif

#if BUILDFLAG(IS_CHROMEOS)
enum AgcState {
  AGC_DISABLED,
  BROWSER_AGC,
  SYSTEM_AGC,
};

class ProcessedLocalAudioSourceIgnoreUiGainsTest
    : public ProcessedLocalAudioSourceBase,
      public testing::WithParamInterface<testing::tuple<bool, AgcState>> {
 public:
  bool IsIgnoreUiGainsEnabled() { return std::get<0>(GetParam()); }

  void SetUp() override {
    if (IsIgnoreUiGainsEnabled()) {
      feature_list_.InitAndEnableFeature(media::kIgnoreUiGains);
    } else {
      feature_list_.InitAndDisableFeature(media::kIgnoreUiGains);
    }

    ProcessedLocalAudioSourceBase::SetUp();
  }

  void SetUpAudioProcessingProperties(AudioProcessingProperties* properties) {
    switch (std::get<1>(GetParam())) {
      case AGC_DISABLED:
        properties->goog_auto_gain_control = false;
        break;
      case BROWSER_AGC:
        properties->goog_auto_gain_control = true;
        properties->system_gain_control_activated = false;
        break;
      case SYSTEM_AGC:
        properties->goog_auto_gain_control = true;
        properties->system_gain_control_activated = true;
        break;
    }
  }

 protected:
  base::test::ScopedFeatureList feature_list_;
};

MATCHER_P2(AudioEffectsAsExpected, flag, agc_state, "") {
  if (flag) {
    switch (agc_state) {
      case AGC_DISABLED:
        return (arg.effects() & media::AudioParameters::IGNORE_UI_GAINS) == 0;
        break;
      case BROWSER_AGC:
      case SYSTEM_AGC:
        return (arg.effects() & media::AudioParameters::IGNORE_UI_GAINS) != 0;
        break;
    }
  } else {
    return (arg.effects() & media::AudioParameters::IGNORE_UI_GAINS) == 0;
  }
}

TEST_P(ProcessedLocalAudioSourceIgnoreUiGainsTest,
       VerifyIgnoreUiGainsStateAsExpected) {
  AudioProcessingProperties properties;
  SetUpAudioProcessingProperties(&properties);
  CreateProcessedLocalAudioSource(properties, 1 /* num_requested_channels */);

  // Connect the track, and expect the MockAudioCapturerSource to be initialized
  // and started by ProcessedLocalAudioSource.
  EXPECT_CALL(*mock_audio_capturer_source(),
              Initialize(AudioEffectsAsExpected(std::get<0>(GetParam()),
                                                std::get<1>(GetParam())),
                         capture_source_callback()));
  EXPECT_CALL(*mock_audio_capturer_source(), SetAutomaticGainControl(true));
  EXPECT_CALL(*mock_audio_capturer_source(), Start())
      .WillOnce(Invoke(
          capture_source_callback(),
          &media::AudioCapturerSource::CaptureCallback::OnCaptureStarted));
  ASSERT_TRUE(audio_source()->ConnectToInitializedTrack(audio_track()));
}

INSTANTIATE_TEST_SUITE_P(
    IgnoreUiGainsTest,
    ProcessedLocalAudioSourceIgnoreUiGainsTest,
    ::testing::Combine(::testing::Bool(),
                       ::testing::ValuesIn({AgcState::AGC_DISABLED,
                                            AgcState::BROWSER_AGC,
                                            AgcState::SYSTEM_AGC})));

enum AecState {
  AEC_DISABLED,
  BROWSER_AEC,
  SYSTEM_AEC,
};

enum VoiceIsolationState {
  kEnabled,
  kDisabled,
  kDefault,
};

class ProcessedLocalAudioSourceVoiceIsolationTest
    : public ProcessedLocalAudioSourceBase,
      public testing::WithParamInterface<
          testing::tuple<bool, bool, VoiceIsolationState, AecState, bool>> {
 public:
  bool IsVoiceIsolationOptionEnabled() { return std::get<0>(GetParam()); }
  bool IsVoiceIsolationSupported() { return std::get<1>(GetParam()); }
  VoiceIsolationState GetVoiceIsolationState() {
    return std::get<2>(GetParam());
  }
  AecState GetAecState() { return std::get<3>(GetParam()); }
  bool IsSystemAecDefaultEnabled() { return std::get<4>(GetParam()); }

  void SetUp() override {
    if (IsVoiceIsolationOptionEnabled()) {
      feature_list_.InitAndEnableFeature(
          media::kCrOSSystemVoiceIsolationOption);
    } else {
      feature_list_.InitAndDisableFeature(
          media::kCrOSSystemVoiceIsolationOption);
    }

    ProcessedLocalAudioSourceBase::SetUp();
  }

  void SetUpAudioProcessingProperties(AudioProcessingProperties* properties) {
    switch (GetAecState()) {
      case AEC_DISABLED:
        properties->echo_cancellation_type = AudioProcessingProperties::
            EchoCancellationType::kEchoCancellationDisabled;
        break;
      case BROWSER_AEC:
        properties->echo_cancellation_type = AudioProcessingProperties::
            EchoCancellationType::kEchoCancellationAec3;
        break;
      case SYSTEM_AEC:
        properties->echo_cancellation_type = AudioProcessingProperties::
            EchoCancellationType::kEchoCancellationSystem;
        break;
    }

    switch (GetVoiceIsolationState()) {
      case VoiceIsolationState::kEnabled:
        properties->voice_isolation = AudioProcessingProperties::
            VoiceIsolationType::kVoiceIsolationEnabled;
        break;
      case VoiceIsolationState::kDisabled:
        properties->voice_isolation = AudioProcessingProperties::
            VoiceIsolationType::kVoiceIsolationDisabled;
        break;
      case VoiceIsolationState::kDefault:
        properties->voice_isolation = AudioProcessingProperties::
            VoiceIsolationType::kVoiceIsolationDefault;
        break;
    }
  }

  void SetUpAudioParameters() {
    blink::MediaStreamDevice modified_device(audio_source()->device());

    if (IsVoiceIsolationSupported()) {
      modified_device.input.set_effects(
          modified_device.input.effects() |
          media::AudioParameters::VOICE_ISOLATION_SUPPORTED);
    }
    if (IsSystemAecDefaultEnabled()) {
      modified_device.input.set_effects(modified_device.input.effects() |
                                        media::AudioParameters::ECHO_CANCELLER);
    }

    audio_source()->SetDevice(modified_device);
  }

 protected:
  base::test::ScopedFeatureList feature_list_;
};

MATCHER_P4(VoiceIsolationAsExpected,
           voice_isolation_option_enabled,
           voice_isolation_supported,
           voice_isolation_state,
           aec_state,
           "") {
  // Only if voice isolation is supported and browser AEC is enabled while voice
  // isolation option feature flag is set, The voice isolation is force to being
  // off. In this case, `CLIENT_CONTROLLED_VOICE_ISOLATION` should be set and
  // `VOICE_ISOLATION` should be off.
  // Otherwise, `CLIENT_CONTROLLED_VOICE_ISOLATION` should be off and
  // `VOICE_ISOLATION` bit is don't-care.
  const bool client_controlled_voice_isolation =
      arg.effects() & media::AudioParameters::CLIENT_CONTROLLED_VOICE_ISOLATION;
  const bool voice_isolation_activated =
      arg.effects() & media::AudioParameters::VOICE_ISOLATION;

  if (voice_isolation_supported && voice_isolation_option_enabled) {
    if (aec_state == BROWSER_AEC) {
      return client_controlled_voice_isolation && !voice_isolation_activated;
    }
    if (voice_isolation_state == VoiceIsolationState::kEnabled) {
      return client_controlled_voice_isolation && voice_isolation_activated;
    }
    if (voice_isolation_state == VoiceIsolationState::kDisabled) {
      return client_controlled_voice_isolation && !voice_isolation_activated;
    }
  }
  return !client_controlled_voice_isolation;
}

TEST_P(ProcessedLocalAudioSourceVoiceIsolationTest,
       VerifyVoiceIsolationStateAsExpected) {
  AudioProcessingProperties properties;
  SetUpAudioProcessingProperties(&properties);
  CreateProcessedLocalAudioSource(properties, 1 /* num_requested_channels */);
  SetUpAudioParameters();

  // Connect the track, and expect the MockAudioCapturerSource to be initialized
  // and started by ProcessedLocalAudioSource.
  EXPECT_CALL(*mock_audio_capturer_source(),
              Initialize(VoiceIsolationAsExpected(
                             IsVoiceIsolationOptionEnabled(),
                             IsVoiceIsolationSupported(),
                             GetVoiceIsolationState(), GetAecState()),
                         capture_source_callback()));
  EXPECT_CALL(*mock_audio_capturer_source(), Start())
      .WillOnce(Invoke(
          capture_source_callback(),
          &media::AudioCapturerSource::CaptureCallback::OnCaptureStarted));
  ASSERT_TRUE(audio_source()->ConnectToInitializedTrack(audio_track()));
}

INSTANTIATE_TEST_SUITE_P(
    VoiceIsolationTest,
    ProcessedLocalAudioSourceVoiceIsolationTest,
    ::testing::Combine(::testing::Bool(),
                       ::testing::Bool(),
                       ::testing::ValuesIn({VoiceIsolationState::kEnabled,
                                            VoiceIsolationState::kDisabled,
                                            VoiceIsolationState::kDefault}),
                       ::testing::ValuesIn({AecState::AEC_DISABLED,
                                            AecState::BROWSER_AEC,
                                            AecState::SYSTEM_AEC}),
                       ::testing::Bool()));

#endif

}  // namespace blink