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