1 /*
2 * Copyright (C) 2025 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "dsp/core/dynamic_range_compression.h"
18 #include <audio_effects/effect_loudnessenhancer.h>
19 #include <audio_utils/dsp_utils.h>
20 #include <gtest/gtest.h>
21 #include <log/log.h>
22 #include <system/audio_effects/audio_effects_test.h>
23
24 using status_t = int32_t;
25 extern audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM;
26 effect_uuid_t loudness_uuid = {0xfa415329, 0x2034, 0x4bea, 0xb5dc,
27 {0x5b, 0x38, 0x1c, 0x8d, 0x1e, 0x2c}};
28
29 using namespace android::audio_utils;
30 using namespace android::effect::utils;
31
32 /*
33 Android 16:
34 expectedEnergydB: -24.771212 energyIndB: -24.739433
35 gaindB: 0.000000 measureddB: 0.000000 energyIndB: -24.739433 energyOutdB: -24.739433
36 gaindB: 1.000000 measureddB: 1.000004 energyIndB: -24.739433 energyOutdB: -23.739429
37 gaindB: 2.000000 measureddB: 2.000002 energyIndB: -24.739433 energyOutdB: -22.739431
38 gaindB: 5.000000 measureddB: 5.000006 energyIndB: -24.739433 energyOutdB: -19.739428
39 gaindB: 10.000000 measureddB: 10.000004 energyIndB: -24.739433 energyOutdB: -14.739429
40 gaindB: 20.000000 measureddB: 13.513464 energyIndB: -24.739433 energyOutdB: -11.225969
41 gaindB: 50.000000 measureddB: 18.649250 energyIndB: -24.739433 energyOutdB: -6.090182
42 gaindB: 100.000000 measureddB: 22.874735 energyIndB: -24.739433 energyOutdB: -1.864698
43 */
44
45 static constexpr audio_channel_mask_t kOutputChannelMasks[] = {
46 AUDIO_CHANNEL_OUT_STEREO,
47 AUDIO_CHANNEL_OUT_5POINT1,
48 AUDIO_CHANNEL_OUT_7POINT1,
49 AUDIO_CHANNEL_OUT_7POINT1POINT4,
50 AUDIO_CHANNEL_OUT_9POINT1POINT6,
51 };
52
53 using LoudnessEnhancerGainParam = std::tuple<int /* channel mask */>;
54
55 enum {
56 GAIN_CHANNEL_MASK_POSITION = 0,
57 //GAIN_ACCUMULATE_POSITION = 1,
58 };
59
60 class LoudnessEnhancerGainTest : public ::testing::TestWithParam<LoudnessEnhancerGainParam> {
61 public:
62
testGain(audio_channel_mask_t channelMask)63 void testGain(audio_channel_mask_t channelMask) {
64 effect_handle_t handle;
65 ASSERT_EQ(0, AUDIO_EFFECT_LIBRARY_INFO_SYM.create_effect(
66 &loudness_uuid, 0 /* sessionId */, 0 /* ioId */, &handle));
67
68 constexpr size_t frameCount = 1024;
69 constexpr uint32_t sampleRate = 48000;
70 const size_t channelCount = audio_channel_count_from_out_mask(channelMask);
71 if (channelCount > FCC_LIMIT) return;
72 constexpr float amplitude = 0.1;
73 const size_t sampleCount = channelCount * frameCount;
74 std::vector<float> originalData(sampleCount);
75 initUniformDistribution(originalData, -amplitude, amplitude);
76 std::vector<float> outData(sampleCount);
77
78 ASSERT_EQ(0, effect_set_config(handle, sampleRate, channelMask));
79 ASSERT_EQ(0, effect_enable(handle));
80
81 // expected energy in dB for a uniform distribution from -amplitude to amplitude.
82 const float expectedEnergydB = energyOfUniformDistribution(-amplitude, amplitude);
83 const float energyIndB = energy(originalData);
84 ALOGD("%s: expectedEnergydB: %f energyIndB: %f", __func__, expectedEnergydB, energyIndB);
85 EXPECT_NEAR(energyIndB, expectedEnergydB, 0.1); // within 0.1dB.
86 float lastMeasuredGaindB = 0;
87 for (int gainmB : { 0, 100, 200, 500, 1'000, 2'000, 5'000, 10'000 }) { // millibel Power
88 ASSERT_EQ(0, effect_set_param(
89 handle, LOUDNESS_ENHANCER_PARAM_TARGET_GAIN_MB, gainmB));
90
91 auto inData = originalData;
92 audio_buffer_t inBuffer{ .frameCount = frameCount, .f32 = inData.data() };
93 audio_buffer_t outBuffer{ .frameCount = frameCount, .f32 = outData.data() };
94 ASSERT_EQ(0, effect_process(handle, &inBuffer, &outBuffer));
95 const float energyOutdB = energy(inData);
96 const float gaindB = gainmB * 1e-2;
97 const float measuredGaindB = energyOutdB - energyIndB;
98
99 // Log our gain and power levels
100 ALOGD("%s: gaindB: %f measureddB: %f energyIndB: %f energyOutdB: %f",
101 __func__, gaindB, measuredGaindB, energyIndB, energyOutdB);
102
103 // Gain curve testing (move to VTS)?
104 if (gaindB == 0) {
105 EXPECT_EQ(energyIndB, energyOutdB);
106 } else if (energyIndB + gaindB < -10.f) {
107 // less than -10dB from overflow, signal does not saturate.
108 EXPECT_NEAR(gaindB, measuredGaindB, 0.1);
109 } else { // effective gain saturates.
110 EXPECT_LT(measuredGaindB, gaindB); // less than the desired gain.
111 EXPECT_GT(measuredGaindB, lastMeasuredGaindB); // more than the previous gain.
112 }
113 lastMeasuredGaindB = measuredGaindB;
114 }
115 ASSERT_EQ(0, AUDIO_EFFECT_LIBRARY_INFO_SYM.release_effect(handle));
116 }
117 };
118
119 /**
120 * The Gain test checks that gain that does not saturate the input signal
121 * will be applied as expected. Gain that would cause the input signal to
122 * exceed the nominal limit is reduced.
123 */
124
TEST_P(LoudnessEnhancerGainTest,gain)125 TEST_P(LoudnessEnhancerGainTest, gain) {
126 testGain(kOutputChannelMasks[std::get<GAIN_CHANNEL_MASK_POSITION>(GetParam())]);
127 }
128
129 INSTANTIATE_TEST_SUITE_P(
130 LoudnessEnhancerTestAll, LoudnessEnhancerGainTest,
131 ::testing::Combine(
132 ::testing::Range(0, (int)std::size(kOutputChannelMasks))),
__anonaf3e4e910202(const testing::TestParamInfo<LoudnessEnhancerGainTest::ParamType>& info) 133 [](const testing::TestParamInfo<LoudnessEnhancerGainTest::ParamType>& info) {
134 const int index = std::get<GAIN_CHANNEL_MASK_POSITION>(info.param);
135 const audio_channel_mask_t channelMask = kOutputChannelMasks[index];
136 const std::string name =
137 std::string(audio_channel_out_mask_to_string(channelMask)) +
138 std::to_string(index);
139 return name;
140 });
141