723 lines
27 KiB
C++
723 lines
27 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
#include "AudioGenerator.h"
|
|
#include "libwebrtcglue/SystemTime.h"
|
|
#include "libwebrtcglue/WebrtcEnvironmentWrapper.h"
|
|
#include "MediaEngineWebRTCAudio.h"
|
|
#include "MediaTrackGraphImpl.h"
|
|
#include "PrincipalHandle.h"
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/NullPrincipal.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsTArray.h"
|
|
|
|
using namespace mozilla;
|
|
using testing::NiceMock;
|
|
using testing::Return;
|
|
|
|
class MockGraph : public MediaTrackGraphImpl {
|
|
public:
|
|
explicit MockGraph(TrackRate aRate)
|
|
: MediaTrackGraphImpl(0, aRate, nullptr, AbstractThread::MainThread()) {
|
|
ON_CALL(*this, OnGraphThread).WillByDefault(Return(true));
|
|
}
|
|
|
|
void Init(uint32_t aChannels) {
|
|
MediaTrackGraphImpl::Init(OFFLINE_THREAD_DRIVER, DIRECT_DRIVER, aChannels);
|
|
|
|
MonitorAutoLock lock(mMonitor);
|
|
// We don't need a graph driver. Advance to
|
|
// LIFECYCLE_WAITING_FOR_TRACK_DESTRUCTION so that the driver never
|
|
// starts. Graph control messages run as in shutdown, synchronously.
|
|
// This permits the main thread part of track initialization through
|
|
// AudioProcessingTrack::Create().
|
|
mLifecycleState = LIFECYCLE_WAITING_FOR_TRACK_DESTRUCTION;
|
|
#ifdef DEBUG
|
|
mCanRunMessagesSynchronously = true;
|
|
#endif
|
|
// Remove this graph's driver since it holds a ref. We are still kept
|
|
// alive by the self-ref. Destroy() must be called to break that cycle if
|
|
// no tracks are created and destroyed.
|
|
mDriver = nullptr;
|
|
}
|
|
|
|
MOCK_CONST_METHOD0(OnGraphThread, bool());
|
|
|
|
protected:
|
|
~MockGraph() = default;
|
|
};
|
|
|
|
// AudioInputProcessing will put extra frames as pre-buffering data to avoid
|
|
// glitchs in non pass-through mode. The main goal of the test is to check how
|
|
// many frames left in the AudioInputProcessing's mSegment in various situations
|
|
// after input data has been processed.
|
|
TEST(TestAudioInputProcessing, Buffering)
|
|
{
|
|
const TrackRate rate = 8000; // So packet size is 80
|
|
const uint32_t channels = 1;
|
|
auto graph = MakeRefPtr<NiceMock<MockGraph>>(rate);
|
|
graph->Init(channels);
|
|
RefPtr track = AudioProcessingTrack::Create(graph);
|
|
|
|
auto aip = MakeRefPtr<AudioInputProcessing>(channels);
|
|
auto envWrapper = WebrtcEnvironmentWrapper::Create(
|
|
mozilla::dom::RTCStatsTimestampMaker::Create());
|
|
aip->SetEnvironmentWrapper(track, std::move(envWrapper));
|
|
|
|
const size_t frames = 72;
|
|
|
|
AudioGenerator<AudioDataValue> generator(channels, rate);
|
|
GraphTime processedTime;
|
|
GraphTime nextTime;
|
|
AudioSegment output;
|
|
MediaEnginePrefs settings;
|
|
settings.mChannels = channels;
|
|
// pref "media.getusermedia.agc2_forced" defaults to true.
|
|
// mAgc would need to be set to something other than kAdaptiveAnalog
|
|
// for mobile, as asserted in AudioInputProcessing::ConfigForPrefs,
|
|
// if gain_controller1 were used.
|
|
settings.mAgc2Forced = true;
|
|
|
|
// Toggle pass-through mode without starting
|
|
{
|
|
EXPECT_EQ(aip->IsPassThrough(graph), true);
|
|
EXPECT_EQ(aip->NumBufferedFrames(graph), 0);
|
|
|
|
settings.mAgcOn = true;
|
|
aip->ApplySettings(graph, nullptr, settings);
|
|
EXPECT_EQ(aip->IsPassThrough(graph), false);
|
|
EXPECT_EQ(aip->NumBufferedFrames(graph), 0);
|
|
|
|
settings.mAgcOn = false;
|
|
aip->ApplySettings(graph, nullptr, settings);
|
|
EXPECT_EQ(aip->NumBufferedFrames(graph), 0);
|
|
}
|
|
|
|
{
|
|
// Need (nextTime - processedTime) = 128 - 0 = 128 frames this round.
|
|
// aip has not started and set to processing mode yet, so output will be
|
|
// filled with silence data directly.
|
|
processedTime = 0;
|
|
nextTime = MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(frames);
|
|
|
|
AudioSegment input;
|
|
generator.Generate(input, nextTime - processedTime);
|
|
|
|
aip->Process(track, processedTime, nextTime, &input, &output);
|
|
EXPECT_EQ(input.GetDuration(), nextTime - processedTime);
|
|
EXPECT_EQ(output.GetDuration(), nextTime);
|
|
EXPECT_EQ(aip->NumBufferedFrames(graph), 0);
|
|
}
|
|
|
|
// Set aip to processing/non-pass-through mode
|
|
settings.mAgcOn = true;
|
|
aip->ApplySettings(graph, nullptr, settings);
|
|
{
|
|
// Need (nextTime - processedTime) = 256 - 128 = 128 frames this round.
|
|
// aip has not started yet, so output will be filled with silence data
|
|
// directly.
|
|
processedTime = nextTime;
|
|
nextTime = MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(2 * frames);
|
|
|
|
AudioSegment input;
|
|
generator.Generate(input, nextTime - processedTime);
|
|
|
|
aip->Process(track, processedTime, nextTime, &input, &output);
|
|
EXPECT_EQ(input.GetDuration(), nextTime - processedTime);
|
|
EXPECT_EQ(output.GetDuration(), nextTime);
|
|
EXPECT_EQ(aip->NumBufferedFrames(graph), 0);
|
|
}
|
|
|
|
// aip has been set to processing mode and is started.
|
|
aip->Start(graph);
|
|
{
|
|
// Need (nextTime - processedTime) = 256 - 256 = 0 frames this round.
|
|
// Process() will return early on 0 frames of input.
|
|
// Pre-buffering is not triggered.
|
|
processedTime = nextTime;
|
|
nextTime = MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(3 * frames);
|
|
|
|
AudioSegment input;
|
|
generator.Generate(input, nextTime - processedTime);
|
|
|
|
aip->Process(track, processedTime, nextTime, &input, &output);
|
|
EXPECT_EQ(input.GetDuration(), nextTime - processedTime);
|
|
EXPECT_EQ(output.GetDuration(), nextTime);
|
|
EXPECT_EQ(aip->NumBufferedFrames(graph), 0);
|
|
}
|
|
|
|
{
|
|
// Need (nextTime - processedTime) = 384 - 256 = 128 frames this round.
|
|
// On receipt of the these first frames, aip will insert 80 frames
|
|
// into its internal buffer as pre-buffering.
|
|
// Process() will take 128 frames from input, packetize and process
|
|
// these frames into floor(128/80) = 1 80-frame packet (48 frames left in
|
|
// packetizer), insert packets into aip's internal buffer, then move 128
|
|
// frames the internal buffer to output, leaving 80 + 80 - 128 = 32 frames
|
|
// in aip's internal buffer.
|
|
processedTime = nextTime;
|
|
nextTime = MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(4 * frames);
|
|
|
|
AudioSegment input;
|
|
generator.Generate(input, nextTime - processedTime);
|
|
|
|
aip->Process(track, processedTime, nextTime, &input, &output);
|
|
EXPECT_EQ(input.GetDuration(), nextTime - processedTime);
|
|
EXPECT_EQ(output.GetDuration(), nextTime);
|
|
EXPECT_EQ(aip->NumBufferedFrames(graph), 32);
|
|
}
|
|
|
|
{
|
|
// Need (nextTime - processedTime) = 384 - 384 = 0 frames this round.
|
|
processedTime = nextTime;
|
|
nextTime = MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(5 * frames);
|
|
|
|
AudioSegment input;
|
|
generator.Generate(input, nextTime - processedTime);
|
|
|
|
aip->Process(track, processedTime, nextTime, &input, &output);
|
|
EXPECT_EQ(input.GetDuration(), nextTime - processedTime);
|
|
EXPECT_EQ(output.GetDuration(), nextTime);
|
|
EXPECT_EQ(aip->NumBufferedFrames(graph), 32);
|
|
}
|
|
|
|
{
|
|
// Need (nextTime - processedTime) = 512 - 384 = 128 frames this round.
|
|
// The Process() aip will take 128 frames from input, packetize and process
|
|
// these frames into floor(128+48/80) = 2 80-frame packet (16 frames left in
|
|
// packetizer), insert packets into aip's internal buffer, then move 128
|
|
// frames the internal buffer to output, leaving 32 + 2*80 - 128 = 64 frames
|
|
// in aip's internal buffer.
|
|
processedTime = nextTime;
|
|
nextTime = MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(6 * frames);
|
|
|
|
AudioSegment input;
|
|
generator.Generate(input, nextTime - processedTime);
|
|
|
|
aip->Process(track, processedTime, nextTime, &input, &output);
|
|
EXPECT_EQ(input.GetDuration(), nextTime - processedTime);
|
|
EXPECT_EQ(output.GetDuration(), nextTime);
|
|
EXPECT_EQ(aip->NumBufferedFrames(graph), 64);
|
|
}
|
|
|
|
// Set aip to pass-through mode
|
|
settings.mAgcOn = false;
|
|
aip->ApplySettings(graph, nullptr, settings);
|
|
{
|
|
// Need (nextTime - processedTime) = 512 - 512 = 0 frames this round.
|
|
// No buffering in pass-through mode
|
|
processedTime = nextTime;
|
|
nextTime = MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(7 * frames);
|
|
|
|
AudioSegment input;
|
|
generator.Generate(input, nextTime - processedTime);
|
|
|
|
aip->Process(track, processedTime, nextTime, &input, &output);
|
|
EXPECT_EQ(input.GetDuration(), nextTime - processedTime);
|
|
EXPECT_EQ(output.GetDuration(), processedTime);
|
|
EXPECT_EQ(aip->NumBufferedFrames(graph), 0);
|
|
}
|
|
|
|
aip->Stop(graph);
|
|
track->Destroy();
|
|
}
|
|
|
|
TEST(TestAudioInputProcessing, ProcessDataWithDifferentPrincipals)
|
|
{
|
|
const TrackRate rate = 48000; // so # of output frames from packetizer is 480
|
|
const uint32_t channels = 2;
|
|
auto graph = MakeRefPtr<NiceMock<MockGraph>>(rate);
|
|
graph->Init(channels);
|
|
RefPtr track = AudioProcessingTrack::Create(graph);
|
|
|
|
auto aip = MakeRefPtr<AudioInputProcessing>(channels);
|
|
auto envWrapper = WebrtcEnvironmentWrapper::Create(
|
|
mozilla::dom::RTCStatsTimestampMaker::Create());
|
|
aip->SetEnvironmentWrapper(track, std::move(envWrapper));
|
|
|
|
AudioGenerator<AudioDataValue> generator(channels, rate);
|
|
|
|
RefPtr<nsIPrincipal> dummy_principal =
|
|
NullPrincipal::CreateWithoutOriginAttributes();
|
|
const PrincipalHandle principal1 = MakePrincipalHandle(dummy_principal.get());
|
|
const PrincipalHandle principal2 =
|
|
MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
|
|
|
|
// Total 4800 frames. It's easier to test with frames of multiples of 480.
|
|
nsTArray<std::pair<TrackTime, PrincipalHandle>> framesWithPrincipal = {
|
|
{100, principal1},
|
|
{200, PRINCIPAL_HANDLE_NONE},
|
|
{300, principal2},
|
|
{400, principal1},
|
|
{440, PRINCIPAL_HANDLE_NONE},
|
|
// 3 packet-size above.
|
|
{480, principal1},
|
|
{480, principal2},
|
|
{480, PRINCIPAL_HANDLE_NONE},
|
|
// 3 packet-size above.
|
|
{500, principal2},
|
|
{490, principal1},
|
|
{600, principal1},
|
|
{330, principal1}
|
|
// 4 packet-size above.
|
|
};
|
|
|
|
// Generate 4800 frames of data with different principals.
|
|
AudioSegment input;
|
|
{
|
|
for (const auto& [duration, principal] : framesWithPrincipal) {
|
|
AudioSegment data;
|
|
generator.Generate(data, duration);
|
|
for (AudioSegment::ChunkIterator it(data); !it.IsEnded(); it.Next()) {
|
|
it->mPrincipalHandle = principal;
|
|
}
|
|
|
|
input.AppendFrom(&data);
|
|
}
|
|
}
|
|
|
|
auto verifyPrincipals = [&](const AudioSegment& data) {
|
|
TrackTime start = 0;
|
|
for (const auto& [duration, principal] : framesWithPrincipal) {
|
|
const TrackTime end = start + duration;
|
|
|
|
AudioSegment slice;
|
|
slice.AppendSlice(data, start, end);
|
|
start = end;
|
|
|
|
for (AudioSegment::ChunkIterator it(slice); !it.IsEnded(); it.Next()) {
|
|
EXPECT_EQ(it->mPrincipalHandle, principal);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Check the principals in audio-processing mode.
|
|
MediaEnginePrefs settings;
|
|
settings.mChannels = channels;
|
|
settings.mAgcOn = true;
|
|
settings.mAgc2Forced = true;
|
|
aip->ApplySettings(graph, nullptr, settings);
|
|
EXPECT_EQ(aip->IsPassThrough(graph), false);
|
|
aip->Start(graph);
|
|
{
|
|
AudioSegment output;
|
|
{
|
|
AudioSegment data;
|
|
aip->Process(track, 0, 4800, &input, &data);
|
|
EXPECT_EQ(input.GetDuration(), 4800);
|
|
EXPECT_EQ(data.GetDuration(), 4800);
|
|
|
|
// Extract another 480 frames to account for delay from pre-buffering.
|
|
EXPECT_EQ(aip->NumBufferedFrames(graph), 480);
|
|
AudioSegment dummy;
|
|
dummy.AppendNullData(480);
|
|
aip->Process(track, 0, 480, &dummy, &data);
|
|
EXPECT_EQ(dummy.GetDuration(), 480);
|
|
EXPECT_EQ(data.GetDuration(), 480 + 4800);
|
|
|
|
// Ignore the pre-buffering silence.
|
|
output.AppendSlice(data, 480, 480 + 4800);
|
|
}
|
|
|
|
verifyPrincipals(output);
|
|
}
|
|
|
|
// Check the principals in pass-through mode.
|
|
settings.mAgcOn = false;
|
|
aip->ApplySettings(graph, nullptr, settings);
|
|
EXPECT_EQ(aip->IsPassThrough(graph), true);
|
|
{
|
|
AudioSegment output;
|
|
aip->Process(track, 0, 4800, &input, &output);
|
|
EXPECT_EQ(input.GetDuration(), 4800);
|
|
EXPECT_EQ(output.GetDuration(), 4800);
|
|
|
|
verifyPrincipals(output);
|
|
}
|
|
|
|
aip->Stop(graph);
|
|
track->Destroy();
|
|
}
|
|
|
|
TEST(TestAudioInputProcessing, Downmixing)
|
|
{
|
|
const TrackRate rate = 44100;
|
|
const uint32_t channels = 4;
|
|
auto graph = MakeRefPtr<NiceMock<MockGraph>>(rate);
|
|
graph->Init(channels);
|
|
RefPtr track = AudioProcessingTrack::Create(graph);
|
|
|
|
auto aip = MakeRefPtr<AudioInputProcessing>(channels);
|
|
auto envWrapper = WebrtcEnvironmentWrapper::Create(
|
|
mozilla::dom::RTCStatsTimestampMaker::Create());
|
|
aip->SetEnvironmentWrapper(track, std::move(envWrapper));
|
|
|
|
const size_t frames = 44100;
|
|
|
|
AudioGenerator<AudioDataValue> generator(channels, rate);
|
|
GraphTime processedTime;
|
|
GraphTime nextTime;
|
|
|
|
MediaEnginePrefs settings;
|
|
settings.mChannels = channels;
|
|
settings.mAgcOn = true;
|
|
settings.mAgc2Forced = true;
|
|
aip->ApplySettings(graph, nullptr, settings);
|
|
EXPECT_EQ(aip->IsPassThrough(graph), false);
|
|
aip->Start(graph);
|
|
|
|
processedTime = 0;
|
|
nextTime = MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(frames);
|
|
|
|
{
|
|
AudioSegment input;
|
|
AudioSegment output;
|
|
generator.Generate(input, nextTime - processedTime);
|
|
|
|
// Intentionally reduce the amplitude of the generated sine wave so there's
|
|
// no chance the max amplitude reaches 1.0, but not enough so that 4
|
|
// channels summed together won't clip.
|
|
input.ApplyVolume(0.9);
|
|
|
|
// Process is going to see that it has 4 channels of input, and is going to
|
|
// downmix to mono, scaling the input by 1/4 in the process.
|
|
// We can't compare the input and output signal because the sine is going to
|
|
// be mangledui
|
|
aip->Process(track, processedTime, nextTime, &input, &output);
|
|
EXPECT_EQ(input.GetDuration(), nextTime - processedTime);
|
|
EXPECT_EQ(output.GetDuration(), nextTime);
|
|
EXPECT_EQ(output.MaxChannelCount(), 1u);
|
|
|
|
// Verify that it doesn't clip: the input signal has likely been mangled by
|
|
// the various processing passes, but at least it shouldn't clip. We know we
|
|
// always have floating point audio here, regardless of the sample-type used
|
|
// by Gecko.
|
|
for (AudioSegment::ChunkIterator iterOutput(output); !iterOutput.IsEnded();
|
|
iterOutput.Next()) {
|
|
const float* const output = iterOutput->ChannelData<float>()[0];
|
|
for (uint32_t i = 0; i < iterOutput->GetDuration(); i++) {
|
|
// Very conservative here, it's likely that the AGC lowers the volume a
|
|
// lot.
|
|
EXPECT_LE(std::abs(output[i]), 0.95);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now, repeat the test in pass-through mode, checking we get the unmodified
|
|
// 4 channels.
|
|
settings.mAgcOn = false;
|
|
aip->ApplySettings(graph, nullptr, settings);
|
|
EXPECT_EQ(aip->IsPassThrough(graph), true);
|
|
|
|
AudioSegment input, output;
|
|
processedTime = nextTime;
|
|
nextTime += MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(frames);
|
|
generator.Generate(input, nextTime - processedTime);
|
|
|
|
aip->Process(track, processedTime, nextTime, &input, &output);
|
|
EXPECT_EQ(input.GetDuration(), nextTime - processedTime);
|
|
EXPECT_EQ(output.GetDuration(), nextTime - processedTime);
|
|
// This time, no downmix: 4 channels of input, 4 channels of output
|
|
EXPECT_EQ(output.MaxChannelCount(), 4u);
|
|
|
|
nsTArray<AudioDataValue> inputLinearized, outputLinearized;
|
|
input.WriteToInterleavedBuffer(inputLinearized, input.MaxChannelCount());
|
|
output.WriteToInterleavedBuffer(outputLinearized, output.MaxChannelCount());
|
|
|
|
// The data should be passed through, and exactly equal.
|
|
for (uint32_t i = 0; i < frames * channels; i++) {
|
|
EXPECT_EQ(inputLinearized[i], outputLinearized[i]);
|
|
}
|
|
|
|
aip->Stop(graph);
|
|
track->Destroy();
|
|
}
|
|
|
|
TEST(TestAudioInputProcessing, DisabledPlatformProcessing)
|
|
{
|
|
const TrackRate rate = 44100;
|
|
const uint32_t channels = 1;
|
|
auto graph = MakeRefPtr<NiceMock<MockGraph>>(rate);
|
|
graph->Init(channels);
|
|
|
|
auto aip = MakeRefPtr<AudioInputProcessing>(channels);
|
|
|
|
MediaEnginePrefs settings;
|
|
settings.mUsePlatformProcessing = false;
|
|
settings.mAecOn = true;
|
|
aip->ApplySettings(graph, nullptr, settings);
|
|
aip->Start(graph);
|
|
|
|
EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
|
|
CUBEB_INPUT_PROCESSING_PARAM_NONE);
|
|
|
|
aip->Stop(graph);
|
|
graph->Destroy();
|
|
}
|
|
|
|
TEST(TestAudioInputProcessing, EnabledPlatformProcessing)
|
|
{
|
|
const TrackRate rate = 44100;
|
|
const uint32_t channels = 1;
|
|
auto graph = MakeRefPtr<NiceMock<MockGraph>>(rate);
|
|
graph->Init(channels);
|
|
|
|
auto aip = MakeRefPtr<AudioInputProcessing>(channels);
|
|
|
|
MediaEnginePrefs settings;
|
|
settings.mUsePlatformProcessing = true;
|
|
settings.mAecOn = true;
|
|
aip->ApplySettings(graph, nullptr, settings);
|
|
aip->Start(graph);
|
|
|
|
EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
|
|
CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
|
|
|
|
aip->Stop(graph);
|
|
graph->Destroy();
|
|
}
|
|
|
|
namespace webrtc {
|
|
bool operator==(const AudioProcessing::Config& aLhs,
|
|
const AudioProcessing::Config& aRhs) {
|
|
return aLhs.echo_canceller.enabled == aRhs.echo_canceller.enabled &&
|
|
(aLhs.gain_controller1.enabled == aRhs.gain_controller1.enabled ||
|
|
aLhs.gain_controller2.enabled == aRhs.gain_controller2.enabled) &&
|
|
aLhs.noise_suppression.enabled == aRhs.noise_suppression.enabled;
|
|
}
|
|
|
|
static std::ostream& operator<<(
|
|
std::ostream& aStream, const webrtc::AudioProcessing::Config& aConfig) {
|
|
aStream << "webrtc::AudioProcessing::Config[";
|
|
bool hadPrior = false;
|
|
if (aConfig.echo_canceller.enabled) {
|
|
aStream << "AEC";
|
|
hadPrior = true;
|
|
}
|
|
if (aConfig.gain_controller1.enabled || aConfig.gain_controller2.enabled) {
|
|
if (hadPrior) {
|
|
aStream << ", ";
|
|
}
|
|
aStream << "AGC";
|
|
}
|
|
if (aConfig.noise_suppression.enabled) {
|
|
if (hadPrior) {
|
|
aStream << ", ";
|
|
}
|
|
aStream << "NS";
|
|
}
|
|
aStream << "]";
|
|
return aStream;
|
|
}
|
|
} // namespace webrtc
|
|
|
|
TEST(TestAudioInputProcessing, PlatformProcessing)
|
|
{
|
|
const TrackRate rate = 44100;
|
|
const uint32_t channels = 1;
|
|
auto graph = MakeRefPtr<NiceMock<MockGraph>>(rate);
|
|
graph->Init(channels);
|
|
|
|
auto aip = MakeRefPtr<AudioInputProcessing>(channels);
|
|
|
|
MediaEnginePrefs settings;
|
|
settings.mUsePlatformProcessing = true;
|
|
settings.mAecOn = true;
|
|
aip->ApplySettings(graph, nullptr, settings);
|
|
aip->Start(graph);
|
|
|
|
webrtc::AudioProcessing::Config echoOnlyConfig;
|
|
echoOnlyConfig.echo_canceller.enabled = true;
|
|
webrtc::AudioProcessing::Config noiseOnlyConfig;
|
|
noiseOnlyConfig.noise_suppression.enabled = true;
|
|
webrtc::AudioProcessing::Config echoNoiseConfig = echoOnlyConfig;
|
|
echoNoiseConfig.noise_suppression.enabled = true;
|
|
|
|
// Config is applied, and platform processing requested.
|
|
EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
|
|
CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
|
|
EXPECT_EQ(aip->AppliedConfig(graph), echoOnlyConfig);
|
|
EXPECT_FALSE(aip->IsPassThrough(graph));
|
|
|
|
// No other constraint requests present.
|
|
aip->NotifySetRequestedInputProcessingParams(
|
|
graph, 1, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
|
|
EXPECT_EQ(aip->AppliedConfig(graph), echoOnlyConfig);
|
|
EXPECT_FALSE(aip->IsPassThrough(graph));
|
|
|
|
// Platform processing params successfully applied.
|
|
aip->NotifySetRequestedInputProcessingParamsResult(
|
|
graph, 1, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
|
|
// Turns off the equivalent APM config.
|
|
EXPECT_EQ(aip->AppliedConfig(graph), webrtc::AudioProcessing::Config());
|
|
EXPECT_TRUE(aip->IsPassThrough(graph));
|
|
|
|
// Request for a response that comes back out-of-order later.
|
|
aip->NotifySetRequestedInputProcessingParams(
|
|
graph, 2, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
|
|
|
|
// Simulate an error after a driver switch.
|
|
aip->NotifySetRequestedInputProcessingParams(
|
|
graph, 3, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
|
|
// Requesting the same config that is already applied; does nothing.
|
|
EXPECT_EQ(aip->AppliedConfig(graph), webrtc::AudioProcessing::Config());
|
|
EXPECT_TRUE(aip->IsPassThrough(graph));
|
|
// Error notification.
|
|
aip->NotifySetRequestedInputProcessingParamsResult(graph, 3,
|
|
Err(CUBEB_ERROR));
|
|
// The APM config is turned back on, and platform processing is requested to
|
|
// be turned off.
|
|
EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
|
|
CUBEB_INPUT_PROCESSING_PARAM_NONE);
|
|
EXPECT_EQ(aip->AppliedConfig(graph), echoOnlyConfig);
|
|
EXPECT_FALSE(aip->IsPassThrough(graph));
|
|
|
|
// The request for turning platform processing off.
|
|
aip->NotifySetRequestedInputProcessingParams(
|
|
graph, 4, CUBEB_INPUT_PROCESSING_PARAM_NONE);
|
|
EXPECT_EQ(aip->AppliedConfig(graph), echoOnlyConfig);
|
|
EXPECT_FALSE(aip->IsPassThrough(graph));
|
|
|
|
// Pretend there was a response for an old request.
|
|
aip->NotifySetRequestedInputProcessingParamsResult(
|
|
graph, 2, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
|
|
// It does nothing since we are requesting NONE now.
|
|
EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
|
|
CUBEB_INPUT_PROCESSING_PARAM_NONE);
|
|
EXPECT_EQ(aip->AppliedConfig(graph), echoOnlyConfig);
|
|
EXPECT_FALSE(aip->IsPassThrough(graph));
|
|
|
|
// Turn it off as requested.
|
|
aip->NotifySetRequestedInputProcessingParamsResult(
|
|
graph, 4, CUBEB_INPUT_PROCESSING_PARAM_NONE);
|
|
EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
|
|
CUBEB_INPUT_PROCESSING_PARAM_NONE);
|
|
EXPECT_EQ(aip->AppliedConfig(graph), echoOnlyConfig);
|
|
EXPECT_FALSE(aip->IsPassThrough(graph));
|
|
|
|
// Test partial support for the requested params.
|
|
settings.mNoiseOn = true;
|
|
aip->ApplySettings(graph, nullptr, settings);
|
|
EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
|
|
CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
|
|
CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
|
|
EXPECT_EQ(aip->AppliedConfig(graph), echoNoiseConfig);
|
|
EXPECT_FALSE(aip->IsPassThrough(graph));
|
|
// The request doesn't change anything.
|
|
aip->NotifySetRequestedInputProcessingParams(
|
|
graph, 5,
|
|
CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
|
|
CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
|
|
EXPECT_EQ(aip->AppliedConfig(graph), echoNoiseConfig);
|
|
EXPECT_FALSE(aip->IsPassThrough(graph));
|
|
// Only noise suppression was supported in the platform.
|
|
aip->NotifySetRequestedInputProcessingParamsResult(
|
|
graph, 5, CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
|
|
// In the APM only echo cancellation is applied.
|
|
EXPECT_EQ(aip->AppliedConfig(graph), echoOnlyConfig);
|
|
EXPECT_FALSE(aip->IsPassThrough(graph));
|
|
|
|
// Test error for partial support.
|
|
aip->NotifySetRequestedInputProcessingParams(
|
|
graph, 6,
|
|
CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
|
|
CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
|
|
EXPECT_EQ(aip->AppliedConfig(graph), echoOnlyConfig);
|
|
EXPECT_FALSE(aip->IsPassThrough(graph));
|
|
aip->NotifySetRequestedInputProcessingParamsResult(graph, 6,
|
|
Err(CUBEB_ERROR));
|
|
// The full config is applied in the APM, and NONE is requested.
|
|
EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
|
|
CUBEB_INPUT_PROCESSING_PARAM_NONE);
|
|
EXPECT_EQ(aip->AppliedConfig(graph), echoNoiseConfig);
|
|
EXPECT_FALSE(aip->IsPassThrough(graph));
|
|
|
|
// Enable platform processing again.
|
|
aip->ApplySettings(graph, nullptr, settings);
|
|
EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
|
|
CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
|
|
CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
|
|
EXPECT_EQ(aip->AppliedConfig(graph), echoNoiseConfig);
|
|
EXPECT_FALSE(aip->IsPassThrough(graph));
|
|
// Request.
|
|
aip->NotifySetRequestedInputProcessingParams(
|
|
graph, 7,
|
|
CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
|
|
CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
|
|
EXPECT_EQ(aip->AppliedConfig(graph), echoNoiseConfig);
|
|
EXPECT_FALSE(aip->IsPassThrough(graph));
|
|
// It succeeded.
|
|
aip->NotifySetRequestedInputProcessingParamsResult(
|
|
graph, 7,
|
|
static_cast<cubeb_input_processing_params>(
|
|
CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
|
|
CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION));
|
|
// No config is applied in the APM, and the full set is requested.
|
|
EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
|
|
CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
|
|
CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
|
|
EXPECT_EQ(aip->AppliedConfig(graph), webrtc::AudioProcessing::Config());
|
|
EXPECT_TRUE(aip->IsPassThrough(graph));
|
|
|
|
// Simulate that another concurrent request was made, i.e. two tracks are
|
|
// using the same device with different processing params, where the
|
|
// intersection of processing params is NONE.
|
|
aip->NotifySetRequestedInputProcessingParams(
|
|
graph, 8, CUBEB_INPUT_PROCESSING_PARAM_NONE);
|
|
// The full config is applied in the APM.
|
|
EXPECT_EQ(aip->AppliedConfig(graph), echoNoiseConfig);
|
|
EXPECT_FALSE(aip->IsPassThrough(graph));
|
|
// The result succeeds, leading to no change since sw processing is already
|
|
// applied.
|
|
aip->NotifySetRequestedInputProcessingParamsResult(
|
|
graph, 8, CUBEB_INPUT_PROCESSING_PARAM_NONE);
|
|
EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
|
|
CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
|
|
CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
|
|
EXPECT_EQ(aip->AppliedConfig(graph), echoNoiseConfig);
|
|
EXPECT_FALSE(aip->IsPassThrough(graph));
|
|
|
|
// The other concurrent request goes away.
|
|
aip->NotifySetRequestedInputProcessingParams(
|
|
graph, 9,
|
|
CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
|
|
CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
|
|
// The full config is still applied in the APM.
|
|
EXPECT_EQ(aip->AppliedConfig(graph), echoNoiseConfig);
|
|
EXPECT_FALSE(aip->IsPassThrough(graph));
|
|
// The result succeeds, leading to no change since sw processing is already
|
|
// applied.
|
|
aip->NotifySetRequestedInputProcessingParamsResult(
|
|
graph, 9,
|
|
static_cast<cubeb_input_processing_params>(
|
|
CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
|
|
CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION));
|
|
EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
|
|
CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
|
|
CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
|
|
EXPECT_EQ(aip->AppliedConfig(graph), webrtc::AudioProcessing::Config());
|
|
EXPECT_TRUE(aip->IsPassThrough(graph));
|
|
|
|
// Changing input track resets the processing params generation. The applied
|
|
// config (AEC, NS) is adapted to the subset applied in the platform (AEC).
|
|
aip->Disconnect(graph);
|
|
aip->NotifySetRequestedInputProcessingParams(
|
|
graph, 1, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
|
|
EXPECT_EQ(aip->AppliedConfig(graph), echoNoiseConfig);
|
|
EXPECT_FALSE(aip->IsPassThrough(graph));
|
|
aip->NotifySetRequestedInputProcessingParamsResult(
|
|
graph, 1, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
|
|
EXPECT_EQ(aip->AppliedConfig(graph), noiseOnlyConfig);
|
|
EXPECT_FALSE(aip->IsPassThrough(graph));
|
|
|
|
aip->Stop(graph);
|
|
graph->Destroy();
|
|
}
|