blob: 6f3b8c4ca113032a464e765fb24c88f0fc0077b6 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fuchsia/media/cpp/fidl.h>
#include <cmath>
#include <gtest/gtest.h>
#include "src/media/audio/lib/test/hermetic_audio_test.h"
// TYPED_TEST_SUITE uses RTTI to print type names, but RTTI is disabled in our build, so
// specialize this function to get nicer test failure messages.
namespace testing::internal {
template <>
std::string GetTypeName<fuchsia::media::AudioRendererPtr>() {
return "AudioRenderer";
}
template <>
std::string GetTypeName<fuchsia::media::AudioCapturerPtr>() {
return "AudioCapturer";
}
} // namespace testing::internal
namespace media::audio::test {
namespace {
template <typename RendererOrCapturerT>
struct RendererOrCapturerTraits {};
template <>
struct RendererOrCapturerTraits<fuchsia::media::AudioRendererPtr> {
static std::string Name() { return "AudioRenderer"; }
static void Create(fuchsia::media::AudioCorePtr& audio_core,
fuchsia::media::AudioRendererPtr& p) {
audio_core->CreateAudioRenderer(p.NewRequest());
}
};
template <>
struct RendererOrCapturerTraits<fuchsia::media::AudioCapturerPtr> {
static std::string Name() { return "AudioCapturer"; }
static void Create(fuchsia::media::AudioCorePtr& audio_core,
fuchsia::media::AudioCapturerPtr& p) {
audio_core->CreateAudioCapturer(false, p.NewRequest());
}
};
} // namespace
template <typename RendererOrCapturerT>
class GainControlTest : public HermeticAudioTest {
protected:
void SetUp() override {
HermeticAudioTest::SetUp();
// Create two gain controllers tied to the same parent object. We will manipulate
// gain_control_1_ while expecting events on both gain controllers.
RendererOrCapturerTraits<RendererOrCapturerT>::Create(audio_core_, parent_);
AddErrorHandler(parent_, RendererOrCapturerTraits<RendererOrCapturerT>::Name());
// Bind gc2 first. If we do this in the opposite order, then commands sent to gc1
// might happen concurrently with the binding of gc2, meaning gc2 will miss updates.
parent_->BindGainControl(gain_control_2_.NewRequest());
parent_->BindGainControl(gain_control_1_.NewRequest());
AddErrorHandler(gain_control_1_,
RendererOrCapturerTraits<RendererOrCapturerT>::Name() + "::GainControl1");
AddErrorHandler(gain_control_2_,
RendererOrCapturerTraits<RendererOrCapturerT>::Name() + "::GainControl2");
// To ensure there is no crosstalk, we create a dummy renderer and capturer
// and a gain control for each, and verify those gain controls are not called.
audio_core_->CreateAudioRenderer(unused_renderer_.NewRequest());
audio_core_->CreateAudioCapturer(false, unused_capturer_.NewRequest());
AddErrorHandler(unused_renderer_, "AudioRenderer (unused)");
AddErrorHandler(unused_capturer_, "AudioCapturer (unused)");
SetUpUnusedGainControl(unused_renderer_gain_control_, unused_renderer_);
SetUpUnusedGainControl(unused_capturer_gain_control_, unused_capturer_);
}
template <typename ParentT>
void SetUpUnusedGainControl(fuchsia::media::audio::GainControlPtr& gc, ParentT& parent) {
parent->BindGainControl(gc.NewRequest());
AddErrorHandler(gc, RendererOrCapturerTraits<ParentT>::Name() + "::GainControl (unused)");
gc.events().OnGainMuteChanged = [](float gain_db, bool muted) {
ADD_FAILURE() << "Unexpected call to unused " << RendererOrCapturerTraits<ParentT>::Name()
<< "'s GainControl: OnGainMuteChanged(" << gain_db << ", " << muted << ")";
};
}
void ExpectGainCallback(float expected_gain_db, bool expected_mute) {
float received_gain_db_1 = NAN;
float received_gain_db_2 = NAN;
bool received_mute_1 = false;
bool received_mute_2 = false;
// We bound gc2 first, so it gets the event first.
gain_control_2_.events().OnGainMuteChanged =
AddCallback("GainControl2::OnGainMuteChanged",
[&received_gain_db_2, &received_mute_2](float gain_db, bool muted) {
received_gain_db_2 = gain_db;
received_mute_2 = muted;
});
gain_control_1_.events().OnGainMuteChanged =
AddCallback("GainControl1::OnGainMuteChanged",
[&received_gain_db_1, &received_mute_1](float gain_db, bool muted) {
received_gain_db_1 = gain_db;
received_mute_1 = muted;
});
ExpectCallback();
EXPECT_EQ(received_gain_db_1, expected_gain_db);
EXPECT_EQ(received_gain_db_2, expected_gain_db);
EXPECT_EQ(received_mute_1, expected_mute);
EXPECT_EQ(received_mute_2, expected_mute);
}
void ExpectParentDisconnect() {
// Disconnecting the parent should also disconnnect the GainControls.
ExpectDisconnects({ErrorHandlerFor(parent_), ErrorHandlerFor(gain_control_1_),
ErrorHandlerFor(gain_control_2_)});
}
void SetGain(float gain_db) { gain_control_1_->SetGain(gain_db); }
void SetMute(bool mute) { gain_control_1_->SetMute(mute); }
RendererOrCapturerT parent_;
fuchsia::media::audio::GainControlPtr gain_control_1_;
fuchsia::media::audio::GainControlPtr gain_control_2_;
fuchsia::media::AudioRendererPtr unused_renderer_;
fuchsia::media::AudioCapturerPtr unused_capturer_;
fuchsia::media::audio::GainControlPtr unused_renderer_gain_control_;
fuchsia::media::audio::GainControlPtr unused_capturer_gain_control_;
};
using GainControlTestTypes =
::testing::Types<fuchsia::media::AudioRendererPtr, fuchsia::media::AudioCapturerPtr>;
TYPED_TEST_SUITE(GainControlTest, GainControlTestTypes);
TYPED_TEST(GainControlTest, SetGain) {
constexpr float expect_gain_db = 20.0f;
this->SetGain(expect_gain_db);
this->ExpectGainCallback(expect_gain_db, false);
this->SetGain(kUnityGainDb);
this->ExpectGainCallback(kUnityGainDb, false);
}
TYPED_TEST(GainControlTest, SetMute) {
bool expect_mute = true;
this->SetMute(expect_mute);
this->ExpectGainCallback(kUnityGainDb, expect_mute);
expect_mute = false;
this->SetMute(expect_mute);
this->ExpectGainCallback(kUnityGainDb, expect_mute);
}
TYPED_TEST(GainControlTest, DuplicateSetGain) {
constexpr float expect_gain_db = 20.0f;
this->SetGain(expect_gain_db);
this->ExpectGainCallback(expect_gain_db, false);
this->SetGain(expect_gain_db);
this->SetMute(true);
// Rather than waiting for "no gain callback", we set an (independent) mute
// value and expect only a single callback that includes the more recent mute.
this->ExpectGainCallback(expect_gain_db, true);
}
TYPED_TEST(GainControlTest, DuplicateSetMute) {
constexpr float expect_gain_db = -42.0f;
this->SetMute(true);
this->ExpectGainCallback(kUnityGainDb, true);
this->SetMute(true);
this->SetGain(expect_gain_db);
// Rather than waiting for "no mute callback", we set an (independent) gain
// value and expect only a single callback that includes the more recent gain.
this->ExpectGainCallback(expect_gain_db, true);
}
// Setting gain too high should cause a disconnect.
TYPED_TEST(GainControlTest, SetGainTooHigh) {
this->SetGain(kTooHighGainDb);
this->ExpectParentDisconnect();
EXPECT_FALSE(this->gain_control_1_.is_bound());
EXPECT_FALSE(this->gain_control_2_.is_bound());
}
// Setting gain too low should cause a disconnect.
TYPED_TEST(GainControlTest, SetGainTooLow) {
this->SetGain(kTooLowGainDb);
this->ExpectParentDisconnect();
EXPECT_FALSE(this->gain_control_1_.is_bound());
EXPECT_FALSE(this->gain_control_2_.is_bound());
}
// Setting gain to NAN should cause a disconnect.
TYPED_TEST(GainControlTest, SetGainNaN) {
this->SetGain(NAN);
this->ExpectParentDisconnect();
EXPECT_FALSE(this->gain_control_1_.is_bound());
EXPECT_FALSE(this->gain_control_2_.is_bound());
}
// TODO(mpuryear): Ramp-related tests (render). Relevant FIDL signature is:
// SetGainWithRamp(float32 gain_db, int64 duration_ns, RampType ramp_type);
// TODO(mpuryear): Validate GainChange notifications of gainramps.
// TODO(mpuryear): Ramp-related negative tests, across all scenarios
} // namespace media::audio::test