#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 {
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 { … };
}
class ProcessedLocalAudioSourceBase : public SimTest { … };
class ProcessedLocalAudioSourceTest
: public ProcessedLocalAudioSourceBase,
public testing::WithParamInterface<ProcessingLocation> { … };
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 );
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,
"") {
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 );
SetUpAudioParameters();
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
}