1 /*
2  * Copyright (C) 2021 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 <audio_utils/ChannelMix.h>
18 #include <audio_utils/Statistics.h>
19 #include <gtest/gtest.h>
20 #include <log/log.h>
21 
22 static constexpr audio_channel_mask_t kChannelPositionMasks[] = {
23     AUDIO_CHANNEL_OUT_FRONT_LEFT, // Legacy: the ChannelMix effect treats MONO as FRONT_LEFT only.
24                                   // The AudioMixer interprets MONO as a special case requiring
25                                   // channel replication, bypassing the ChannelMix effect.
26     AUDIO_CHANNEL_OUT_FRONT_CENTER,
27     AUDIO_CHANNEL_OUT_STEREO,
28     AUDIO_CHANNEL_OUT_2POINT1,
29     AUDIO_CHANNEL_OUT_2POINT0POINT2,
30     AUDIO_CHANNEL_OUT_QUAD, // AUDIO_CHANNEL_OUT_QUAD_BACK
31     AUDIO_CHANNEL_OUT_QUAD_SIDE,
32     AUDIO_CHANNEL_OUT_SURROUND,
33     AUDIO_CHANNEL_OUT_2POINT1POINT2,
34     AUDIO_CHANNEL_OUT_3POINT0POINT2,
35     AUDIO_CHANNEL_OUT_PENTA,
36     AUDIO_CHANNEL_OUT_3POINT1POINT2,
37     AUDIO_CHANNEL_OUT_5POINT1, // AUDIO_CHANNEL_OUT_5POINT1_BACK
38     AUDIO_CHANNEL_OUT_5POINT1_SIDE,
39     AUDIO_CHANNEL_OUT_6POINT1,
40     AUDIO_CHANNEL_OUT_5POINT1POINT2,
41     AUDIO_CHANNEL_OUT_7POINT1,
42     AUDIO_CHANNEL_OUT_5POINT1POINT4,
43     AUDIO_CHANNEL_OUT_7POINT1POINT2,
44     AUDIO_CHANNEL_OUT_7POINT1POINT4,
45     AUDIO_CHANNEL_OUT_13POINT_360RA,
46     AUDIO_CHANNEL_OUT_22POINT2,
47     audio_channel_mask_t(AUDIO_CHANNEL_OUT_22POINT2
48             | AUDIO_CHANNEL_OUT_FRONT_WIDE_LEFT | AUDIO_CHANNEL_OUT_FRONT_WIDE_RIGHT),
49 };
50 
51 constexpr float COEF_25 = 0.2508909536f;
52 constexpr float COEF_35 = 0.3543928915f;
53 constexpr float COEF_36 = 0.3552343859f;
54 constexpr float COEF_61 = 0.6057043428f;
55 
56 constexpr inline float kScaleFromChannelIdxLeft[] = {
57     1.f,       // AUDIO_CHANNEL_OUT_FRONT_LEFT            = 0x1u,
58     0.f,       // AUDIO_CHANNEL_OUT_FRONT_RIGHT           = 0x2u,
59     M_SQRT1_2, // AUDIO_CHANNEL_OUT_FRONT_CENTER          = 0x4u,
60     0.5f,      // AUDIO_CHANNEL_OUT_LOW_FREQUENCY         = 0x8u,
61     M_SQRT1_2, // AUDIO_CHANNEL_OUT_BACK_LEFT             = 0x10u,
62     0.f,       // AUDIO_CHANNEL_OUT_BACK_RIGHT            = 0x20u,
63     COEF_61,   // AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER  = 0x40u,
64     COEF_25,   // AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x80u,
65     0.5f,      // AUDIO_CHANNEL_OUT_BACK_CENTER           = 0x100u,
66     M_SQRT1_2, // AUDIO_CHANNEL_OUT_SIDE_LEFT             = 0x200u,
67     0.f,       // AUDIO_CHANNEL_OUT_SIDE_RIGHT            = 0x400u,
68     COEF_36,   // AUDIO_CHANNEL_OUT_TOP_CENTER            = 0x800u,
69     1.f,       // AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT        = 0x1000u,
70     M_SQRT1_2, // AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER      = 0x2000u,
71     0.f,       // AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT       = 0x4000u,
72     M_SQRT1_2, // AUDIO_CHANNEL_OUT_TOP_BACK_LEFT         = 0x8000u,
73     COEF_35,   // AUDIO_CHANNEL_OUT_TOP_BACK_CENTER       = 0x10000u,
74     0.f,       // AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT        = 0x20000u,
75     COEF_61,   // AUDIO_CHANNEL_OUT_TOP_SIDE_LEFT         = 0x40000u,
76     0.f,       // AUDIO_CHANNEL_OUT_TOP_SIDE_RIGHT        = 0x80000u,
77     1.f,       // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_LEFT     = 0x100000u,
78     M_SQRT1_2, // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_CENTER   = 0x200000u,
79     0.f,       // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_RIGHT    = 0x400000u,
80     0.f,       // AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2       = 0x800000u,
81     M_SQRT1_2, // AUDIO_CHANNEL_OUT_FRONT_WIDE_LEFT       = 0x1000000u,
82     0.f,       // AUDIO_CHANNEL_OUT_FRONT_WIDE_RIGHT      = 0x2000000u,
83 };
84 
85 constexpr inline float kScaleFromChannelIdxRight[] = {
86     0.f,       // AUDIO_CHANNEL_OUT_FRONT_LEFT            = 0x1u,
87     1.f,       // AUDIO_CHANNEL_OUT_FRONT_RIGHT           = 0x2u,
88     M_SQRT1_2, // AUDIO_CHANNEL_OUT_FRONT_CENTER          = 0x4u,
89     0.5f,      // AUDIO_CHANNEL_OUT_LOW_FREQUENCY         = 0x8u,
90     0.f,       // AUDIO_CHANNEL_OUT_BACK_LEFT             = 0x10u,
91     M_SQRT1_2, // AUDIO_CHANNEL_OUT_BACK_RIGHT            = 0x20u,
92     COEF_25,   // AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER  = 0x40u,
93     COEF_61,   // AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x80u,
94     0.5f,      // AUDIO_CHANNEL_OUT_BACK_CENTER           = 0x100u,
95     0.f,       // AUDIO_CHANNEL_OUT_SIDE_LEFT             = 0x200u,
96     M_SQRT1_2, // AUDIO_CHANNEL_OUT_SIDE_RIGHT            = 0x400u,
97     COEF_36,   // AUDIO_CHANNEL_OUT_TOP_CENTER            = 0x800u,
98     0.f,       // AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT        = 0x1000u,
99     M_SQRT1_2, // AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER      = 0x2000u,
100     1.f,       // AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT       = 0x4000u,
101     0.f,       // AUDIO_CHANNEL_OUT_TOP_BACK_LEFT         = 0x8000u,
102     COEF_35,   // AUDIO_CHANNEL_OUT_TOP_BACK_CENTER       = 0x10000u,
103     M_SQRT1_2, // AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT        = 0x20000u,
104     0.f,       // AUDIO_CHANNEL_OUT_TOP_SIDE_LEFT         = 0x40000u,
105     COEF_61,   // AUDIO_CHANNEL_OUT_TOP_SIDE_RIGHT        = 0x80000u,
106     0.f,       // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_LEFT     = 0x100000u,
107     M_SQRT1_2, // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_CENTER   = 0x200000u,
108     1.f,       // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_RIGHT    = 0x400000u,
109     M_SQRT1_2, // AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2       = 0x800000u,
110     0.f,       // AUDIO_CHANNEL_OUT_FRONT_WIDE_LEFT       = 0x1000000u,
111     M_SQRT1_2, // AUDIO_CHANNEL_OUT_FRONT_WIDE_RIGHT      = 0x2000000u,
112 };
113 
114 // Our near expectation is 16x the bit that doesn't fit the mantissa.
115 // this works so long as we add values close in exponent with each other
116 // realizing that errors accumulate as the sqrt of N (random walk, lln, etc).
117 #define EXPECT_NEAR_EPSILON(e, v) EXPECT_NEAR((e), (v), \
118         abs((e) * std::numeric_limits<std::decay_t<decltype(e)>>::epsilon() * 8))
119 
120 template<typename T>
channelStatistics(const std::vector<T> & input,size_t channels)121 static auto channelStatistics(const std::vector<T>& input, size_t channels) {
122     std::vector<android::audio_utils::Statistics<T>> result(channels);
123     const size_t frames = input.size() / channels;
124     if (frames > 0) {
125         const float *fptr = input.data();
126         for (size_t i = 0; i < frames; ++i) {
127             for (size_t j = 0; j < channels; ++j) {
128                 result[j].add(*fptr++);
129             }
130         }
131     }
132     return result;
133 }
134 
135 using ChannelMixParam = std::tuple<int /* channel mask */, int /* 0 = replace, 1 = accumulate */>;
136 class ChannelMixTest : public ::testing::TestWithParam<ChannelMixParam> {
137 public:
138 
testBalance(audio_channel_mask_t channelMask,bool accumulate)139     void testBalance(audio_channel_mask_t channelMask, bool accumulate) {
140         using namespace ::android::audio_utils::channels;
141 
142         size_t frames = 100; // set to an even number (2, 4, 6 ... ) stream alternates +1, -1.
143         constexpr unsigned outChannels = FCC_2;
144         unsigned inChannels = audio_channel_count_from_out_mask(channelMask);
145         std::vector<float> input(frames * inChannels);
146         std::vector<float> output(frames * outChannels);
147 
148         double savedPower[32][FCC_2]{};
149         for (unsigned i = 0, channel = channelMask; channel != 0; ++i) {
150             const int index = __builtin_ctz(channel);
151             ASSERT_LT((size_t)index, ChannelMix::MAX_INPUT_CHANNELS_SUPPORTED);
152             const int pairIndex = pairIdxFromChannelIdx(index);
153             const AUDIO_GEOMETRY_SIDE side = sideFromChannelIdx(index);
154             const int channelBit = 1 << index;
155             channel &= ~channelBit;
156 
157             // Generate a +0.5, -0.5 alternating stream in one channel, which has variance 0.25f
158             auto indata = input.data();
159             for (unsigned j = 0; j < frames; ++j) {
160                 for (unsigned k = 0; k < inChannels; ++k) {
161                     *indata++ = (k == i) ? (j & 1 ? -0.5f : 0.5f) : 0;
162                 }
163             }
164 
165             // Add an offset to the output data - this is ignored if replace instead of accumulate.
166             // This must not cause the output to exceed [-1.f, 1.f] otherwise clamping will occur.
167             auto outdata = output.data();
168             for (unsigned j = 0; j < frames; ++j) {
169                 for (unsigned k = 0; k < outChannels; ++k) {
170                     *outdata++ = 0.5f;
171                 }
172             }
173 
174             // Do the channel mix
175             ChannelMix(channelMask).process(input.data(), output.data(), frames, accumulate);
176 
177             // if we accumulate, we need to subtract the initial data offset.
178             if (accumulate) {
179                 outdata = output.data();
180                 for (unsigned j = 0; j < frames; ++j) {
181                     for (unsigned k = 0; k < outChannels; ++k) {
182                         *outdata++ -= 0.5f;
183                     }
184                 }
185             }
186 
187             // renormalize the stream to unit amplitude (and unity variance).
188             outdata = output.data();
189             for (unsigned j = 0; j < frames; ++j) {
190                 for (unsigned k = 0; k < outChannels; ++k) {
191                     *outdata++ *= 2.f;
192                 }
193             }
194 
195             auto stats = channelStatistics(output, FCC_2);
196             // printf("power: %s %s\n", stats[0].toString().c_str(), stats[1].toString().c_str());
197             double power[FCC_2] = { stats[0].getPopVariance(), stats[1].getPopVariance() };
198 
199             // Check symmetric power for pair channels on exchange of left/right position.
200             // to do this, we save previous power measurements.
201             if (pairIndex >= 0 && pairIndex < index) {
202                 EXPECT_NEAR_EPSILON(power[0], savedPower[pairIndex][1]);
203                 EXPECT_NEAR_EPSILON(power[1], savedPower[pairIndex][0]);
204             }
205             savedPower[index][0] = power[0];
206             savedPower[index][1] = power[1];
207 
208             // Confirm exactly the mix amount prescribed by the existing ChannelMix effect.
209             // For future changes to the ChannelMix effect, the nearness needs to be relaxed
210             // to compare behavior S or earlier.
211 
212             constexpr float POWER_TOLERANCE = 0.001;
213             const float expectedPower =
214                     kScaleFromChannelIdxLeft[index] * kScaleFromChannelIdxLeft[index]
215                     + kScaleFromChannelIdxRight[index] * kScaleFromChannelIdxRight[index];
216             EXPECT_NEAR(expectedPower, power[0] + power[1], POWER_TOLERANCE);
217             switch (side) {
218             case AUDIO_GEOMETRY_SIDE_LEFT:
219                 if (channelBit == AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER) {
220                     break;
221                 }
222                 EXPECT_EQ(0.f, power[1]);
223                 break;
224             case AUDIO_GEOMETRY_SIDE_RIGHT:
225                 if (channelBit == AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER) {
226                     break;
227                 }
228                 EXPECT_EQ(0.f, power[0]);
229                 break;
230             case AUDIO_GEOMETRY_SIDE_CENTER:
231                 if (channelBit == AUDIO_CHANNEL_OUT_LOW_FREQUENCY) {
232                     if (channelMask & AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2) {
233                         EXPECT_EQ(0.f, power[1]);
234                         break;
235                     } else {
236                         EXPECT_NEAR_EPSILON(power[0], power[1]); // always true
237                         EXPECT_NEAR(expectedPower, power[0] + power[1], POWER_TOLERANCE);
238                         break;
239                     }
240                 } else if (channelBit == AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2) {
241                     EXPECT_EQ(0.f, power[0]);
242                     EXPECT_NEAR(expectedPower, power[1], POWER_TOLERANCE);
243                     break;
244                 }
245                 EXPECT_NEAR_EPSILON(power[0], power[1]);
246                 break;
247             }
248         }
249     }
250 };
251 
TEST_P(ChannelMixTest,basic)252 TEST_P(ChannelMixTest, basic) {
253     testBalance(kChannelPositionMasks[std::get<0>(GetParam())], (bool)std::get<1>(GetParam()));
254 }
255 
256 static const char *kName1[] = {"_replace_", "_accumulate_"};
257 
258 INSTANTIATE_TEST_SUITE_P(
259         ChannelMixTestAll, ChannelMixTest,
260         ::testing::Combine(
261                 ::testing::Range(0, (int)std::size(kChannelPositionMasks)),
262                 ::testing::Range(0, 2)
263                 ),
__anon559d6e6b0102(const testing::TestParamInfo<ChannelMixTest::ParamType>& info) 264         [](const testing::TestParamInfo<ChannelMixTest::ParamType>& info) {
265             const int index = std::get<0>(info.param);
266             const audio_channel_mask_t channelMask = kChannelPositionMasks[index];
267             const std::string name = std::string(audio_channel_out_mask_to_string(channelMask)) +
268                     kName1[std::get<1>(info.param)] + std::to_string(index);
269             return name;
270         });
271 
TEST(channelmix,input_channel_mask)272 TEST(channelmix, input_channel_mask) {
273     using namespace ::android::audio_utils::channels;
274     ChannelMix channelMix(AUDIO_CHANNEL_NONE);
275 
276     ASSERT_EQ(AUDIO_CHANNEL_NONE, channelMix.getInputChannelMask());
277     ASSERT_TRUE(channelMix.setInputChannelMask(AUDIO_CHANNEL_OUT_STEREO));
278     ASSERT_EQ(AUDIO_CHANNEL_OUT_STEREO, channelMix.getInputChannelMask());
279 }
280