blob: c4bb6878c2db355471baa74abcf61020b88bdace [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 <fbl/algorithm.h>
#include "src/media/audio/audio_core/mixer/test/audio_result.h"
#include "src/media/audio/audio_core/mixer/test/mixer_tests_shared.h"
namespace media::audio::test {
// Convenience abbreviation within this source file to shorten names
using Resampler = ::media::audio::Mixer::Resampler;
//
// Gain tests - how does the Gain object respond when given values close to its
// maximum or minimum; does it correctly cache; do values combine to form Unity
// gain. Is data scaling accurately performed, and is it adequately linear? Do
// our gains and accumulators behave as expected when they overflow?
//
// Gain tests using AScale and the Gain object only
//
class GainTest : public testing::Test {
protected:
void SetUp() override {
testing::Test::SetUp();
// Setup a render usage for the gain object during test.
fuchsia::media::Usage usage;
usage.set_render_usage(fuchsia::media::AudioRenderUsage::MEDIA);
gain_.SetUsage(std::move(usage));
gain_.SetRenderUsageGain(fuchsia::media::AudioRenderUsage::MEDIA, Gain::kUnityGainDb);
rate_1khz_output_ = TimelineRate(1000, ZX_SEC(1));
}
void TestUnityGain(float source_gain_db, float dest_gain_db) {
gain_.SetSourceGain(source_gain_db);
EXPECT_EQ(Gain::kUnityScale, gain_.GetGainScale(dest_gain_db));
gain_.SetDestGain(dest_gain_db);
EXPECT_FALSE(gain_.IsSilent());
EXPECT_TRUE(gain_.IsUnity());
}
void TestMinMuteGain(float source_gain_db, float dest_gain_db) {
gain_.SetSourceGain(source_gain_db);
EXPECT_EQ(Gain::kMuteScale, gain_.GetGainScale(dest_gain_db));
gain_.SetDestGain(dest_gain_db);
EXPECT_EQ(Gain::kMuteScale, gain_.GetGainScale());
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsSilent());
}
void TestMinMuteGain(float source_gain_db, float dest_gain_db, float usage_gain_db) {
gain_.SetSourceGain(source_gain_db);
gain_.SetDestGain(dest_gain_db);
gain_.SetRenderUsageGain(fuchsia::media::AudioRenderUsage::MEDIA, usage_gain_db);
EXPECT_EQ(Gain::kMuteScale, gain_.GetGainScale());
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsSilent());
}
// Used for debugging purposes.
static void DisplayScaleVals(const Gain::AScale* scale_arr, uint32_t buf_size) {
printf("\n ********************************************************");
printf("\n **************************************************************");
printf("\n *** Displaying raw scale array data for length %5d ***", buf_size);
printf("\n **************************************************************");
for (uint32_t idx = 0; idx < buf_size; ++idx) {
if (idx % 10 == 0) {
printf("\n [%d] ", idx);
}
printf("%.7f ", scale_arr[idx]);
}
printf("\n **************************************************************");
printf("\n ********************************************************");
printf("\n");
}
Gain gain_;
TimelineRate rate_1khz_output_;
};
class MuteTest : public GainTest {};
class RampTest : public GainTest {};
class ScaleArrayTest : public GainTest {};
TEST_F(GainTest, Defaults) {
EXPECT_EQ(gain_.GetGainScale(), Gain::kUnityScale);
EXPECT_TRUE(gain_.IsUnity());
EXPECT_FALSE(gain_.IsSilent());
EXPECT_FALSE(gain_.IsRamping());
}
// Test the internally-used inline func that converts AScale gain to dB.
TEST_F(GainTest, GainScaleToDb) {
// Unity scale is 0.0dB (no change).
EXPECT_EQ(Gain::ScaleToDb(Gain::kUnityScale), Gain::kUnityGainDb);
// 10x scale-up in amplitude (by definition) is exactly +20.0dB.
EXPECT_EQ(Gain::ScaleToDb(Gain::kUnityScale * 10.0f), 20.0f);
// 1/100x scale-down in amplitude (by definition) is exactly -40.0dB.
EXPECT_EQ(Gain::ScaleToDb(Gain::kUnityScale * 0.01f), -40.0f);
// 1/2x scale-down by calculation: -6.020600... dB.
const float half_scale = -6.0206001f;
// Because of float imprecision, use our Compare...() with float tolerance.
EXPECT_TRUE(
CompareBufferToVal(&half_scale, Gain::ScaleToDb(Gain::kUnityScale * 0.5f), 1, true, true));
}
// Test the inline function that converts a numerical value to dB.
TEST_F(GainTest, DoubleToDb) {
EXPECT_EQ(Gain::DoubleToDb(Gain::kUnityScale), 0.0); // Unity is 0 dB
EXPECT_EQ(Gain::DoubleToDb(Gain::kUnityScale * 100.0),
40.0); // 100x is 40 dB
EXPECT_EQ(Gain::DoubleToDb(Gain::kUnityScale * 0.1), -20.0); // 10% is -20 dB
EXPECT_GE(Gain::DoubleToDb(Gain::kUnityScale * 0.5),
-6.0206 * 1.000001); // 50% is roughly -6.0206 dB
EXPECT_LE(Gain::DoubleToDb(Gain::kUnityScale * 0.5),
-6.0206 * 0.999999); // FP representation => 2 comps
}
// Do source and destination gains correctly combine to produce unity scaling?
TEST_F(GainTest, Unity) {
TestUnityGain(Gain::kUnityGainDb, Gain::kUnityGainDb);
// These positive/negative values should sum to 0.0: UNITY
TestUnityGain(Gain::kMaxGainDb / 2, -Gain::kMaxGainDb / 2);
TestUnityGain(-Gain::kMaxGainDb, Gain::kMaxGainDb);
}
// Gain caches any previously set source gain, using it if needed.
// This verifies the default and caching behavior of the Gain object
TEST_F(GainTest, SourceGainCaching) {
Gain expect_gain;
Gain::AScale amplitude_scale, expect_amplitude_scale;
// Set expect_amplitude_scale to a value that represents -6.0 dB.
expect_gain.SetSourceGain(6.0f);
expect_amplitude_scale = expect_gain.GetGainScale(-12.0f);
// If Render gain defaults to 0.0, this represents -6.0 dB too.
amplitude_scale = gain_.GetGainScale(-6.0f);
EXPECT_EQ(expect_amplitude_scale, amplitude_scale);
// Now set a different renderer gain that will be cached (+3.0).
gain_.SetSourceGain(3.0f);
amplitude_scale = gain_.GetGainScale(-3.0f);
EXPECT_EQ(Gain::kUnityScale, amplitude_scale);
// If Render gain is cached val of +3, then combo should be Unity.
amplitude_scale = gain_.GetGainScale(-3.0f);
EXPECT_EQ(Gain::kUnityScale, amplitude_scale);
// Try another Output gain; with cached +3 this should equate to -6dB.
gain_.SetDestGain(-9.0f);
EXPECT_EQ(expect_amplitude_scale, gain_.GetGainScale());
// Render gain cached +3 and Output gain non-cached -3 should lead to Unity.
EXPECT_EQ(Gain::kUnityScale, gain_.GetGainScale(-3.0f));
// Cached Output gain should still be -9, leading to -6dB.
EXPECT_EQ(expect_amplitude_scale, gain_.GetGainScale());
}
// We independently limit stream and device gains to kMaxGainDb/0, respectively.
// MTWN-70 concerns Gain's statefulness. Does it need this complexity?
TEST_F(GainTest, MaxClamp) {
// Verify that Usage gain is clamped to 0.0db
gain_.SetRenderUsageGain(fuchsia::media::AudioRenderUsage::MEDIA, Gain::kMaxGainDb);
EXPECT_EQ(Gain::kUnityScale, gain_.GetGainScale());
// Renderer Gain of 2 * kMaxGainDb is clamped to kMaxGainDb (+24 dB).
gain_.SetSourceGain(Gain::kMaxGainDb * 2);
EXPECT_EQ(Gain::kMaxScale, gain_.GetGainScale(Gain::kUnityGainDb));
// This combination (24.05 dB) is clamped to 24.0dB.
gain_.SetSourceGain(Gain::kMaxGainDb);
EXPECT_EQ(Gain::kMaxScale, gain_.GetGainScale(0.05f));
// System limits renderer gain to kMaxGainDb, even when sum is less than 0.
// Renderer Gain +36dB (clamped to +24dB) plus system Gain -48dB ==> -24dB.
constexpr float kScale24DbDown = 0.0630957344f;
gain_.SetSourceGain(Gain::kMaxGainDb * 1.5f);
gain_.SetDestGain(-2 * Gain::kMaxGainDb);
EXPECT_EQ(kScale24DbDown, gain_.GetGainScale());
EXPECT_FALSE(gain_.IsUnity());
EXPECT_FALSE(gain_.IsSilent());
// AudioCore limits master to 0dB, but Gain object handles up to kMaxGainDb.
// Dest also clamps to +24dB: source(-48dB) + dest(+36dB=>24dB) becomes -24dB.
gain_.SetSourceGain(-2 * Gain::kMaxGainDb);
gain_.SetDestGain(Gain::kMaxGainDb * 1.5f);
EXPECT_EQ(kScale24DbDown, gain_.GetGainScale());
EXPECT_FALSE(gain_.IsUnity());
EXPECT_FALSE(gain_.IsSilent());
}
// System independently limits stream and master/device Gains to kMinGainDb
// (-160dB). Assert scale is zero, if either (or combo) are kMinGainDb or less.
TEST_F(GainTest, MinMute) {
// First, test for source/dest interactions.
// if OutputGain <= kMinGainDb, scale must be 0, regardless of renderer gain.
TestMinMuteGain(-2 * Gain::kMinGainDb, Gain::kMinGainDb);
// if renderer gain <= kMinGainDb, scale must be 0, regardless of Output gain.
TestMinMuteGain(Gain::kMinGainDb, Gain::kMaxGainDb * 1.2);
// if sum of renderer gain and Output gain <= kMinGainDb, scale should be 0.
// Output gain is just slightly above MinGain; renderer takes us below it.
TestMinMuteGain(-2.0f, Gain::kMinGainDb + 1.0f);
// Next, test for source/usage/dest interactions.
// Check if source alone mutes.
TestMinMuteGain(Gain::kMinGainDb, Gain::kUnityGainDb + 1, Gain::kUnityGainDb);
TestMinMuteGain(Gain::kMinGainDb, Gain::kUnityGainDb, Gain::kUnityGainDb + 1);
// Check if dest alone mutes.
TestMinMuteGain(Gain::kUnityGainDb + 1, Gain::kMinGainDb, Gain::kUnityGainDb);
TestMinMuteGain(Gain::kUnityGainDb, Gain::kMinGainDb, Gain::kUnityGainDb + 1);
// Check if usage alone mutes.
TestMinMuteGain(Gain::kUnityGainDb + 1, Gain::kUnityGainDb, Gain::kMinGainDb);
TestMinMuteGain(Gain::kUnityGainDb, Gain::kUnityGainDb + 1, Gain::kMinGainDb);
// Check if usage + source mutes.
TestMinMuteGain(Gain::kMinGainDb / 2, Gain::kUnityGainDb, Gain::kMinGainDb / 2);
// Check if usage + dest mutes.
TestMinMuteGain(Gain::kUnityGainDb, Gain::kMinGainDb / 2, Gain::kMinGainDb / 2);
// Check if all three adding up to mute mutes.
TestMinMuteGain(Gain::kMinGainDb / 4, Gain::kMinGainDb / 4, Gain::kMinGainDb / 2);
}
// Mute-related tests
//
// These tests use SetMute itself (as opposed to Gain tests that use gain values
// that exceed our lower limit and hence produce silence).
//
TEST_F(MuteTest, SourceGainThenMute) {
gain_.SetSourceGain(Gain::kMaxGainDb);
EXPECT_GT(gain_.GetGainScale(), Gain::kUnityScale);
EXPECT_FALSE(gain_.IsUnity());
EXPECT_FALSE(gain_.IsSilent());
gain_.SetSourceMute(true);
EXPECT_EQ(gain_.GetGainScale(), Gain::kMuteScale);
EXPECT_TRUE(gain_.IsSilent());
gain_.SetSourceMute(false);
EXPECT_GT(gain_.GetGainScale(), Gain::kUnityScale);
EXPECT_FALSE(gain_.IsSilent());
gain_.SetDestMute(true);
EXPECT_EQ(gain_.GetGainScale(), Gain::kMuteScale);
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsSilent());
gain_.SetDestMute(false);
EXPECT_GT(gain_.GetGainScale(), Gain::kUnityScale);
EXPECT_FALSE(gain_.IsSilent());
}
TEST_F(MuteTest, DestGainThenMute) {
gain_.SetDestGain(Gain::kMaxGainDb);
EXPECT_GT(gain_.GetGainScale(), Gain::kUnityScale);
EXPECT_FALSE(gain_.IsSilent());
gain_.SetSourceMute(true);
EXPECT_EQ(gain_.GetGainScale(), Gain::kMuteScale);
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsSilent());
gain_.SetDestMute(true);
EXPECT_EQ(gain_.GetGainScale(), Gain::kMuteScale);
EXPECT_TRUE(gain_.IsSilent());
gain_.SetSourceMute(false);
EXPECT_EQ(gain_.GetGainScale(), Gain::kMuteScale);
EXPECT_TRUE(gain_.IsSilent());
gain_.SetDestMute(false);
EXPECT_GT(gain_.GetGainScale(), Gain::kUnityScale);
EXPECT_FALSE(gain_.IsUnity());
EXPECT_FALSE(gain_.IsSilent());
}
TEST_F(MuteTest, SourceMuteThenGain) {
gain_.SetSourceMute(true);
EXPECT_EQ(gain_.GetGainScale(), Gain::kMuteScale);
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsSilent());
gain_.SetDestGain(Gain::kMaxGainDb);
EXPECT_TRUE(gain_.IsSilent());
gain_.SetSourceGain(Gain::kMinGainDb);
EXPECT_TRUE(gain_.IsSilent());
gain_.SetSourceGain(Gain::kUnityGainDb);
EXPECT_EQ(gain_.GetGainScale(), Gain::kMuteScale);
EXPECT_TRUE(gain_.IsSilent());
}
TEST_F(MuteTest, DestMuteThenGain) {
gain_.SetDestMute(true);
EXPECT_EQ(gain_.GetGainScale(), Gain::kMuteScale);
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsSilent());
gain_.SetDestGain(Gain::kMaxGainDb);
EXPECT_TRUE(gain_.IsSilent());
gain_.SetSourceGain(Gain::kMinGainDb);
EXPECT_TRUE(gain_.IsSilent());
gain_.SetSourceGain(Gain::kUnityGainDb);
EXPECT_EQ(gain_.GetGainScale(), Gain::kMuteScale);
EXPECT_TRUE(gain_.IsSilent());
}
// Ramp-related tests
//
// Setting a ramp with zero duration is the same as an immediate gain change.
TEST_F(RampTest, SetRampWithNoDurationChangesGain) {
gain_.SetSourceGain(-11.0f);
gain_.SetDestGain(-1.0f);
gain_.SetSourceGainWithRamp(+1.0f, 0);
EXPECT_TRUE(gain_.IsUnity());
EXPECT_FALSE(gain_.IsRamping());
EXPECT_FALSE(gain_.IsSilent());
}
// Setting a ramp with non-zero duration does not take effect until Advance.
TEST_F(RampTest, SetRampWithDurationDoesntChangeGain) {
gain_.SetSourceGain(24.0f);
gain_.SetDestGain(-24.0f);
gain_.SetSourceGainWithRamp(Gain::kMinGainDb, 1);
// Expect pre-ramp conditions
EXPECT_TRUE(gain_.GetGainScale() == Gain::kUnityScale);
EXPECT_FALSE(gain_.IsSilent());
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsRamping());
}
// If a ramp-up is active/pending, then IsSilent should not be true.
TEST_F(RampTest, RampingUpIsNeverSilent) {
gain_.SetSourceGain(-150.0f);
gain_.SetDestGain(-22.0f);
gain_.SetSourceGainWithRamp(+22.0f, ZX_SEC(1));
// Expect pre-ramp conditions
EXPECT_FALSE(gain_.IsSilent());
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsRamping());
}
// If we are ramping-down and already silent, IsSilent should remain true.
TEST_F(RampTest, SilentAndRampingDownIsSilent) {
gain_.SetDestGain(-160.0f);
gain_.SetSourceGainWithRamp(-1.0f, ZX_SEC(1));
// Expect pre-ramp conditions
EXPECT_TRUE(gain_.IsSilent());
EXPECT_TRUE(gain_.IsRamping());
EXPECT_FALSE(gain_.IsUnity());
}
// If a ramp is active/pending, then IsUnity should never be true.
TEST_F(RampTest, RampingIsNeverUnity) {
gain_.SetSourceGain(Gain::kUnityGainDb);
gain_.SetDestGain(Gain::kUnityGainDb);
EXPECT_TRUE(gain_.IsUnity());
gain_.SetSourceGainWithRamp(-1.0f, ZX_SEC(1));
// Expect pre-ramp conditions
EXPECT_FALSE(gain_.IsSilent());
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsRamping());
}
// If the beginning and end of a ramp are the same, it isn't ramping.
TEST_F(RampTest, FlatIsntRamping) {
gain_.SetSourceGain(Gain::kUnityGainDb);
gain_.SetDestGain(-20.0f);
gain_.SetSourceGainWithRamp(0.0f, ZX_SEC(1));
// Expect pre-ramp conditions
EXPECT_FALSE(gain_.IsSilent());
EXPECT_FALSE(gain_.IsUnity());
EXPECT_FALSE(gain_.IsRamping());
}
// Upon Advance, we should see a change in the instantaneous GetGainScale().
TEST_F(RampTest, AdvanceChangesGain) {
gain_.SetSourceGain(-150.0f);
gain_.SetDestGain(-13.0f);
gain_.SetSourceGainWithRamp(+13.0f, 1);
// Advance far beyond end of ramp -- 10 msec (10 frames@1kHz) vs. 1 nsec.
gain_.Advance(10, rate_1khz_output_);
// Expect post-ramp conditions
EXPECT_FALSE(gain_.IsSilent());
EXPECT_TRUE(gain_.IsUnity());
EXPECT_FALSE(gain_.IsRamping());
}
TEST_F(RampTest, SourceMuteRampIsRampingButSilent) {
gain_.SetSourceMute(true);
EXPECT_FALSE(gain_.IsRamping());
gain_.SetSourceGainWithRamp(-20.0f, ZX_MSEC(9));
EXPECT_TRUE(gain_.IsRamping());
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsSilent());
}
TEST_F(RampTest, DestMuteRampIsRampingButSilent) {
gain_.SetDestMute(true);
gain_.SetSourceGainWithRamp(10.0f, ZX_MSEC(5));
EXPECT_TRUE(gain_.IsRamping());
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsSilent());
}
TEST_F(RampTest, RampSourceMuteIsRampingButSilent) {
gain_.SetSourceGainWithRamp(-20.0f, ZX_MSEC(9));
gain_.SetSourceMute(true);
EXPECT_TRUE(gain_.IsRamping());
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsSilent());
}
TEST_F(RampTest, RampDestMuteIsRampingButSilent) {
gain_.SetSourceGainWithRamp(-20.0f, ZX_MSEC(9));
gain_.SetDestMute(true);
EXPECT_TRUE(gain_.IsRamping());
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsSilent());
}
// ScaleArray-related tests
//
// If no ramp, all vals returned by GetScaleArray should equal GetGainScale().
TEST_F(ScaleArrayTest, GetScaleArrayNoRampEqualsGetScale) {
Gain::AScale scale_arr[3];
gain_.SetDestGain(-42.0f);
gain_.SetSourceGain(-68.0f);
gain_.GetScaleArray(scale_arr, fbl::count_of(scale_arr), rate_1khz_output_);
Gain::AScale expect_scale = gain_.GetGainScale();
EXPECT_TRUE(CompareBufferToVal(scale_arr, expect_scale, fbl::count_of(scale_arr)));
EXPECT_FALSE(gain_.IsUnity());
EXPECT_FALSE(gain_.IsRamping());
EXPECT_FALSE(gain_.IsSilent());
}
// Validate when ramp and GetScaleArray are identical length.
TEST_F(ScaleArrayTest, GetScaleArrayRamp) {
Gain::AScale scale_arr[6];
Gain::AScale expect_arr[6] = {1.0, 0.82, 0.64, 0.46, 0.28, 0.1};
gain_.SetSourceGainWithRamp(-20, ZX_MSEC(5));
gain_.GetScaleArray(scale_arr, fbl::count_of(scale_arr), rate_1khz_output_);
// When comparing buffers, do it within the tolerance of 32-bit float
EXPECT_TRUE(CompareBuffers(scale_arr, expect_arr, fbl::count_of(scale_arr), true, true));
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsRamping());
EXPECT_FALSE(gain_.IsSilent());
}
// Validate when ramp duration is greater than GetScaleArray.
TEST_F(ScaleArrayTest, GetScaleArrayLongRamp) {
Gain::AScale scale_arr[4]; // At 1kHz this is less than the ramp duration.
Gain::AScale expect_arr[4] = {1.0, 0.901, 0.802, 0.703};
gain_.SetSourceGainWithRamp(-40, ZX_MSEC(10));
gain_.GetScaleArray(scale_arr, fbl::count_of(scale_arr), rate_1khz_output_);
// When comparing buffers, do it within the tolerance of 32-bit float
EXPECT_TRUE(CompareBuffers(scale_arr, expect_arr, fbl::count_of(scale_arr), true, true));
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsRamping());
EXPECT_FALSE(gain_.IsSilent());
}
// Validate when ramp duration is shorter than GetScaleArray.
TEST_F(ScaleArrayTest, GetScaleArrayShortRamp) {
Gain::AScale scale_arr[9]; // At 1kHz this is longer than the ramp duration.
Gain::AScale expect_arr[9] = {1.0, 0.82, 0.64, 0.46, 0.28, 0.1, 0.1, 0.1, 0.1};
gain_.SetSourceGainWithRamp(-20, ZX_MSEC(5));
gain_.GetScaleArray(scale_arr, fbl::count_of(scale_arr), rate_1khz_output_);
// When comparing buffers, do it within the tolerance of 32-bit float
EXPECT_TRUE(CompareBuffers(scale_arr, expect_arr, fbl::count_of(scale_arr), true, true));
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsRamping());
EXPECT_FALSE(gain_.IsSilent());
}
// Successive GetScaleArray calls without Advance should return same results.
TEST_F(ScaleArrayTest, GetScaleArrayWithoutAdvance) {
gain_.SetSourceGainWithRamp(-123.45678, ZX_MSEC(9));
Gain::AScale scale_arr[10];
gain_.GetScaleArray(scale_arr, fbl::count_of(scale_arr), rate_1khz_output_);
Gain::AScale scale_arr2[10];
gain_.GetScaleArray(scale_arr2, fbl::count_of(scale_arr2), rate_1khz_output_);
EXPECT_TRUE(CompareBuffers(scale_arr, scale_arr2, fbl::count_of(scale_arr)));
}
// Advances that exceed ramp durations should lead to end-to-ramp conditions.
TEST_F(ScaleArrayTest, GetScaleArrayBigAdvance) {
Gain::AScale scale_arr[6];
Gain::AScale expect = Gain::kUnityScale * 2;
gain_.SetSourceGainWithRamp(6.0205999, ZX_MSEC(5));
gain_.GetScaleArray(scale_arr, fbl::count_of(scale_arr), rate_1khz_output_);
EXPECT_FALSE(CompareBufferToVal(scale_arr, expect, fbl::count_of(scale_arr), false, true));
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsRamping());
EXPECT_FALSE(gain_.IsSilent());
gain_.Advance(rate_1khz_output_.Scale(ZX_SEC(10)), rate_1khz_output_);
gain_.GetScaleArray(scale_arr, fbl::count_of(scale_arr), rate_1khz_output_);
EXPECT_TRUE(CompareBufferToVal(scale_arr, expect, fbl::count_of(scale_arr)));
EXPECT_FALSE(gain_.IsSilent());
EXPECT_FALSE(gain_.IsRamping());
EXPECT_FALSE(gain_.IsUnity());
}
// Clearing a ramp should reset any in-process ramps.
TEST_F(ScaleArrayTest, ClearSourceRamp) {
Gain::AScale scale_arr[6];
Gain::AScale scale_arr2[6];
gain_.SetSourceGainWithRamp(-30.1029995, ZX_MSEC(5));
gain_.GetScaleArray(scale_arr, fbl::count_of(scale_arr), rate_1khz_output_);
EXPECT_FALSE(
CompareBufferToVal(scale_arr, Gain::kUnityScale, fbl::count_of(scale_arr), false, true));
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsRamping());
EXPECT_FALSE(gain_.IsSilent());
// After clearing the ramp, scale_arr should be constant.
gain_.ClearSourceRamp();
gain_.GetScaleArray(scale_arr, fbl::count_of(scale_arr), rate_1khz_output_);
EXPECT_TRUE(CompareBufferToVal(scale_arr, Gain::kUnityScale, fbl::count_of(scale_arr)));
EXPECT_FALSE(gain_.IsSilent());
EXPECT_FALSE(gain_.IsRamping());
EXPECT_TRUE(gain_.IsUnity());
// Without a ramp, scale_arr should be constant even after Advance.
gain_.Advance(10, rate_1khz_output_);
gain_.GetScaleArray(scale_arr2, fbl::count_of(scale_arr2), rate_1khz_output_);
EXPECT_TRUE(CompareBufferToVal(scale_arr2, Gain::kUnityScale, fbl::count_of(scale_arr2)));
EXPECT_FALSE(gain_.IsRamping());
EXPECT_TRUE(gain_.IsUnity());
EXPECT_FALSE(gain_.IsSilent());
}
// After partial Advance through a ramp, instantaneous gain should be accurate.
TEST_F(ScaleArrayTest, AdvanceHalfwayThroughRamp) {
Gain::AScale scale_arr[4]; // At 1kHz this is less than the ramp duration.
Gain::AScale expect_arr[4];
gain_.SetSourceGainWithRamp(-20.0f, ZX_MSEC(9));
gain_.GetScaleArray(scale_arr, fbl::count_of(scale_arr), rate_1khz_output_);
Gain::AScale expect_scale = Gain::kUnityScale;
EXPECT_EQ(gain_.GetGainScale(), expect_scale);
// When comparing buffers, do it within the tolerance of 32-bit float
for (auto& val : expect_arr) {
val = expect_scale;
expect_scale -= 0.1;
}
EXPECT_TRUE(CompareBuffers(scale_arr, expect_arr, fbl::count_of(scale_arr), true, true));
EXPECT_FALSE(gain_.IsSilent());
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsRamping());
// Advance only partially through the duration of the ramp.
const uint32_t kFramesToAdvance = 2;
gain_.Advance(kFramesToAdvance, rate_1khz_output_);
gain_.GetScaleArray(scale_arr, fbl::count_of(scale_arr), rate_1khz_output_);
// DisplayScaleVals(scale_arr, fbl::count_of(scale_arr));
expect_scale = expect_arr[kFramesToAdvance];
EXPECT_TRUE(CompareBufferToVal(&expect_scale, gain_.GetGainScale(), 1, true, true));
for (auto& val : expect_arr) {
val = expect_scale;
expect_scale -= 0.1;
}
EXPECT_TRUE(CompareBuffers(scale_arr, expect_arr, fbl::count_of(scale_arr), true, true));
EXPECT_TRUE(gain_.IsRamping());
EXPECT_FALSE(gain_.IsUnity());
EXPECT_FALSE(gain_.IsSilent());
}
TEST_F(ScaleArrayTest, MuteDuringRamp) {
Gain::AScale scale_arr[4]; // At 1kHz this is less than the ramp duration.
Gain::AScale expect_arr[4];
gain_.SetSourceGainWithRamp(-20.0f, ZX_MSEC(9));
gain_.GetScaleArray(scale_arr, fbl::count_of(scale_arr), rate_1khz_output_);
Gain::AScale expect_scale = Gain::kUnityScale;
EXPECT_EQ(gain_.GetGainScale(), expect_scale);
gain_.SetSourceMute(true);
EXPECT_EQ(gain_.GetGainScale(), Gain::kMuteScale);
for (auto& val : expect_arr) {
val = expect_scale;
expect_scale -= 0.1;
}
// When comparing buffers, do it within the tolerance of 32-bit float
EXPECT_TRUE(CompareBuffers(scale_arr, expect_arr, fbl::count_of(scale_arr), true, true));
// Advance only partially through the duration of the ramp.
const uint32_t kFramesToAdvance = 2;
gain_.Advance(kFramesToAdvance, rate_1khz_output_);
EXPECT_FALSE(gain_.IsUnity());
EXPECT_TRUE(gain_.IsRamping());
EXPECT_TRUE(gain_.IsSilent());
gain_.SetSourceMute(false);
gain_.GetScaleArray(scale_arr, fbl::count_of(scale_arr), rate_1khz_output_);
// DisplayScaleVals(scale_arr, fbl::count_of(scale_arr));
expect_scale = expect_arr[kFramesToAdvance];
EXPECT_TRUE(CompareBufferToVal(&expect_scale, gain_.GetGainScale(), 1, true, true));
for (auto& val : expect_arr) {
val = expect_scale;
expect_scale -= 0.1;
}
EXPECT_TRUE(CompareBuffers(scale_arr, expect_arr, fbl::count_of(scale_arr), true, true));
EXPECT_TRUE(gain_.IsRamping());
EXPECT_FALSE(gain_.IsUnity());
EXPECT_FALSE(gain_.IsSilent());
}
//
// Data scaling tests
//
// Tests using Gain via a Mixer object, in a mixing environment.
//
// These validate the actual scaling of audio data, including overflow and any
// truncation or rounding (above just checks the generation of scale values).
//
// When doing direct bit-for-bit comparisons in these tests, we must factor in
// the left-shift biasing that is done while converting input data into the
// internal format of our accumulator. For this reason, all "expect" values are
// specified at a higher-than-needed precision of 24-bit, and then normalized
// down to the actual pipeline width.
//
// The 'MixGain' tests involve gain-scaling in the context of mixing (as opposed
// to earlier tests that directly probe the Gain object in isolation).
// Verify whether per-stream gain interacts linearly with accumulation buffer.
TEST(MixGain, Scaling_Linearity) {
int16_t source[] = {0x0CE4, 0x0CCC, 0x23, 4, -0x0E, -0x19, -0x0CCC, -0x0CDB};
float accum[8];
// Validate that +20.00 dB leads to exactly 10x in value (within limits)
float stream_gain_db = 20.0f;
auto mixer = SelectMixer(fuchsia::media::AudioSampleFormat::SIGNED_16, 1, 44100, 1, 44100,
Resampler::SampleAndHold);
DoMix(mixer.get(), source, accum, false, fbl::count_of(accum), stream_gain_db);
float expect[] = {0x080E8000, 0x07FF8000, 0x015E000, 0x00028000,
-0x0008C000, -0x000FA000, -0x07FF8000, -0x0808E000};
NormalizeInt28ToPipelineBitwidth(expect, fbl::count_of(expect));
EXPECT_TRUE(CompareBuffers(accum, expect, fbl::count_of(accum)));
//
// How precisely linear are our gain stages, mathematically?
// Validate that -12.0411998 dB leads to exactly 0.25x in value
stream_gain_db = -12.0411998f;
mixer = SelectMixer(fuchsia::media::AudioSampleFormat::SIGNED_16, 1, 44100, 1, 44100,
Resampler::SampleAndHold);
DoMix(mixer.get(), source, accum, false, fbl::count_of(accum), stream_gain_db);
float expect2[] = {0x00339000, 0x00333000, 0x00008C00, 0x00001000,
-0x00003800, -0x00006400, -0x00333000, -0x00336C00};
NormalizeInt28ToPipelineBitwidth(expect2, fbl::count_of(expect2));
EXPECT_TRUE(CompareBuffers(accum, expect2, fbl::count_of(accum)));
}
// How does our gain scaling respond to scale values close to the limits?
// Using 16-bit inputs, verify the behavior of our Gain object when given the
// closest-to-Unity and closest-to-Mute scale values.
TEST(MixGain, Scaling_Precision) {
int16_t max_source[] = {0x7FFF, -0x8000}; // max/min 16-bit signed values.
float accum[2];
// kMinGainDbUnity is the lowest (furthest-from-Unity) with no observable
// attenuation on full-scale (i.e. the smallest indistinguishable from Unity).
// At this gain_scale, audio should be unchanged.
auto mixer = SelectMixer(fuchsia::media::AudioSampleFormat::SIGNED_16, 1, 48000, 1, 48000,
Resampler::SampleAndHold);
DoMix(mixer.get(), max_source, accum, false, fbl::count_of(accum), AudioResult::kMinGainDbUnity);
// At this gain_scale, resulting audio should be unchanged.
float max_expect1[] = {0x07FFF000, -0x08000000}; // left-shift source by 12.
NormalizeInt28ToPipelineBitwidth(max_expect1, fbl::count_of(max_expect1));
EXPECT_TRUE(CompareBuffers(accum, max_expect1, fbl::count_of(accum)));
// This is the highest (closest-to-Unity) AScale with an observable effect on
// full-scale (i.e. the largest sub-Unity AScale distinguishable from Unity).
mixer = SelectMixer(fuchsia::media::AudioSampleFormat::SIGNED_16, 1, 48000, 1, 48000,
Resampler::SampleAndHold);
DoMix(mixer.get(), max_source, accum, false, fbl::count_of(accum),
AudioResult::kMaxGainDbNonUnity);
// Float32 has 25-bit precision (not 28), hence our min delta is 8 (not 1).
float max_expect2[] = {0x07FFEFF8, -0x07FFFFF8};
NormalizeInt28ToPipelineBitwidth(max_expect2, fbl::count_of(max_expect2));
EXPECT_TRUE(CompareBuffers(accum, max_expect2, fbl::count_of(accum)));
// kMinGainDbNonMute is the lowest (closest-to-zero) at which audio is not
// silenced (i.e. the smallest that is distinguishable from Mute). Although
// the results may be smaller than we can represent in our 28-bit test data
// representation, they are still non-zero and thus validate our scalar limit.
int16_t min_source[] = {1, -1};
mixer = SelectMixer(fuchsia::media::AudioSampleFormat::SIGNED_16, 1, 48000, 1, 48000,
Resampler::SampleAndHold);
DoMix(mixer.get(), min_source, accum, false, fbl::count_of(accum),
AudioResult::kMinGainDbNonMute);
// The method used elsewhere in this file for expected result arrays (28-bit
// fixed-point, normalized into float) cannot precisely express these values.
// Nonetheless, they are present and non-zero!
float min_expect[] = {3.051763215e-13, -3.051763215e-13};
EXPECT_TRUE(CompareBuffers(accum, min_expect, fbl::count_of(accum)));
//
// kMaxGainDbMute is the highest (furthest-from-Mute) scalar that silences
// full scale data (i.e. the largest AScale that is indistinguishable from
// Mute). Consider an AScale value corresponding to ever-so-slightly above
// -160dB: if this increment is small enough, the float32 cannot discern it
// and treats it as -160dB, our limit for "automatically mute". Per a mixer
// optimization, if gain is Mute-equivalent, we skip mixing altogether. This
// is equivalent to setting 'accumulate' and adding zeroes, so set that flag
// here and expect no change in the accumulator, even with max inputs.
mixer = SelectMixer(fuchsia::media::AudioSampleFormat::SIGNED_16, 1, 48000, 1, 48000,
Resampler::SampleAndHold);
DoMix(mixer.get(), max_source, accum, true, fbl::count_of(accum), AudioResult::kMaxGainDbMute);
EXPECT_TRUE(CompareBuffers(accum, min_expect, fbl::count_of(accum)));
}
//
// Tests on our multi-stream accumulator -- can values temporarily exceed the
// max or min values for an individual stream; at what value doese the
// accumulator hit its limit, and at that limit does it clamp or rollover?
//
// Can accumulator result exceed the max range of individual streams?
TEST(MixGain, Accumulator) {
int16_t source[] = {0x7FFF, -0x8000};
float accum[] = {0x07FFF000, -0x08000000};
float expect[] = {0x0FFFE000, -0x10000000};
float expect2[] = {0x17FFD000, -0x18000000};
// When mixed, these far exceed any int16 range
NormalizeInt28ToPipelineBitwidth(accum, fbl::count_of(accum));
NormalizeInt28ToPipelineBitwidth(expect, fbl::count_of(expect));
NormalizeInt28ToPipelineBitwidth(expect2, fbl::count_of(expect2));
// These values exceed the per-stream range of int16
auto mixer = SelectMixer(fuchsia::media::AudioSampleFormat::SIGNED_16, 1, 48000, 1, 48000,
Resampler::SampleAndHold);
DoMix(mixer.get(), source, accum, true, fbl::count_of(accum));
EXPECT_TRUE(CompareBuffers(accum, expect, fbl::count_of(accum)));
// these values even exceed uint16
mixer = SelectMixer(fuchsia::media::AudioSampleFormat::SIGNED_16, 2, 48000, 2, 48000,
Resampler::SampleAndHold);
DoMix(mixer.get(), source, accum, true, 1);
EXPECT_TRUE(CompareBuffers(accum, expect2, fbl::count_of(accum)));
}
// Our mixer contains an optimization in which it skips mixing operations if it
// detects that gain is below a certain threshold (regardless of "accumulate").
void TestAccumulatorClear(Resampler sampler_type) {
int16_t source[] = {-32768, 32767, -16384, 16383};
float accum[] = {-32768, 32767, -16384, 16383};
float expect[] = {-32768, 32767, -16384, 16383};
auto mixer =
SelectMixer(fuchsia::media::AudioSampleFormat::SIGNED_16, 1, 48000, 1, 48000, sampler_type);
// Use a gain guaranteed to silence any signal -- Gain::kMinGainDb.
DoMix(mixer.get(), source, accum, true, fbl::count_of(accum), Gain::kMinGainDb);
EXPECT_TRUE(CompareBuffers(accum, expect, fbl::count_of(accum)));
// When accumulate = false but gain is sufficiently low, overwriting previous
// contents is skipped. This should lead to the same results as above.
mixer =
SelectMixer(fuchsia::media::AudioSampleFormat::SIGNED_16, 1, 48000, 1, 48000, sampler_type);
DoMix(mixer.get(), source, accum, false, fbl::count_of(accum), Gain::kMinGainDb);
EXPECT_TRUE(CompareBuffers(accum, expect, fbl::count_of(accum)));
}
// Validate the SampleAndHold interpolator for this behavior.
TEST(MixGain, Accumulator_Clear_Point) { TestAccumulatorClear(Resampler::SampleAndHold); }
// Validate the same assertions, with LinearInterpolation interpolator.
TEST(MixGain, Accumulator_Clear_Linear) { TestAccumulatorClear(Resampler::LinearInterpolation); }
} // namespace media::audio::test