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 <vector>
18
19 #include "EffectDownmix.h"
20
21 #include <audio_utils/channels.h>
22 #include <audio_utils/primitives.h>
23 #include <audio_utils/Statistics.h>
24 #include <gtest/gtest.h>
25 #include <log/log.h>
26
27 extern audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM;
28 static constexpr audio_channel_mask_t kChannelPositionMasks[] = {
29 AUDIO_CHANNEL_OUT_FRONT_LEFT, // Legacy: the downmix effect treats MONO as FRONT_LEFT only.
30 // The AudioMixer interprets MONO as a special case requiring
31 // channel replication, bypassing the downmix effect.
32 AUDIO_CHANNEL_OUT_FRONT_CENTER,
33 AUDIO_CHANNEL_OUT_STEREO,
34 AUDIO_CHANNEL_OUT_2POINT1,
35 AUDIO_CHANNEL_OUT_2POINT0POINT2,
36 AUDIO_CHANNEL_OUT_QUAD, // AUDIO_CHANNEL_OUT_QUAD_BACK
37 AUDIO_CHANNEL_OUT_QUAD_SIDE,
38 AUDIO_CHANNEL_OUT_SURROUND,
39 AUDIO_CHANNEL_OUT_2POINT1POINT2,
40 AUDIO_CHANNEL_OUT_3POINT0POINT2,
41 AUDIO_CHANNEL_OUT_PENTA,
42 AUDIO_CHANNEL_OUT_3POINT1POINT2,
43 AUDIO_CHANNEL_OUT_5POINT1, // AUDIO_CHANNEL_OUT_5POINT1_BACK
44 AUDIO_CHANNEL_OUT_5POINT1_SIDE,
45 AUDIO_CHANNEL_OUT_6POINT1,
46 AUDIO_CHANNEL_OUT_5POINT1POINT2,
47 AUDIO_CHANNEL_OUT_7POINT1,
48 AUDIO_CHANNEL_OUT_5POINT1POINT4,
49 AUDIO_CHANNEL_OUT_7POINT1POINT2,
50 AUDIO_CHANNEL_OUT_7POINT1POINT4,
51 AUDIO_CHANNEL_OUT_13POINT_360RA,
52 AUDIO_CHANNEL_OUT_22POINT2,
53 audio_channel_mask_t(AUDIO_CHANNEL_OUT_22POINT2
54 | AUDIO_CHANNEL_OUT_FRONT_WIDE_LEFT | AUDIO_CHANNEL_OUT_FRONT_WIDE_RIGHT),
55 };
56
57 constexpr float COEF_25 = 0.2508909536f;
58 constexpr float COEF_35 = 0.3543928915f;
59 constexpr float COEF_36 = 0.3552343859f;
60 constexpr float COEF_61 = 0.6057043428f;
61
62 constexpr inline float kScaleFromChannelIdxLeft[] = {
63 1.f, // AUDIO_CHANNEL_OUT_FRONT_LEFT = 0x1u,
64 0.f, // AUDIO_CHANNEL_OUT_FRONT_RIGHT = 0x2u,
65 M_SQRT1_2, // AUDIO_CHANNEL_OUT_FRONT_CENTER = 0x4u,
66 0.5f, // AUDIO_CHANNEL_OUT_LOW_FREQUENCY = 0x8u,
67 M_SQRT1_2, // AUDIO_CHANNEL_OUT_BACK_LEFT = 0x10u,
68 0.f, // AUDIO_CHANNEL_OUT_BACK_RIGHT = 0x20u,
69 COEF_61, // AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x40u,
70 COEF_25, // AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x80u,
71 0.5f, // AUDIO_CHANNEL_OUT_BACK_CENTER = 0x100u,
72 M_SQRT1_2, // AUDIO_CHANNEL_OUT_SIDE_LEFT = 0x200u,
73 0.f, // AUDIO_CHANNEL_OUT_SIDE_RIGHT = 0x400u,
74 COEF_36, // AUDIO_CHANNEL_OUT_TOP_CENTER = 0x800u,
75 1.f, // AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT = 0x1000u,
76 M_SQRT1_2, // AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER = 0x2000u,
77 0.f, // AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT = 0x4000u,
78 M_SQRT1_2, // AUDIO_CHANNEL_OUT_TOP_BACK_LEFT = 0x8000u,
79 COEF_35, // AUDIO_CHANNEL_OUT_TOP_BACK_CENTER = 0x10000u,
80 0.f, // AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT = 0x20000u,
81 COEF_61, // AUDIO_CHANNEL_OUT_TOP_SIDE_LEFT = 0x40000u,
82 0.f, // AUDIO_CHANNEL_OUT_TOP_SIDE_RIGHT = 0x80000u,
83 1.f, // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_LEFT = 0x100000u,
84 M_SQRT1_2, // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_CENTER = 0x200000u,
85 0.f, // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_RIGHT = 0x400000u,
86 0.f, // AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2 = 0x800000u,
87 M_SQRT1_2, // AUDIO_CHANNEL_OUT_FRONT_WIDE_LEFT = 0x1000000u,
88 0.f, // AUDIO_CHANNEL_OUT_FRONT_WIDE_RIGHT = 0x2000000u,
89 };
90
91 constexpr inline float kScaleFromChannelIdxRight[] = {
92 0.f, // AUDIO_CHANNEL_OUT_FRONT_LEFT = 0x1u,
93 1.f, // AUDIO_CHANNEL_OUT_FRONT_RIGHT = 0x2u,
94 M_SQRT1_2, // AUDIO_CHANNEL_OUT_FRONT_CENTER = 0x4u,
95 0.5f, // AUDIO_CHANNEL_OUT_LOW_FREQUENCY = 0x8u,
96 0.f, // AUDIO_CHANNEL_OUT_BACK_LEFT = 0x10u,
97 M_SQRT1_2, // AUDIO_CHANNEL_OUT_BACK_RIGHT = 0x20u,
98 COEF_25, // AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x40u,
99 COEF_61, // AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x80u,
100 0.5f, // AUDIO_CHANNEL_OUT_BACK_CENTER = 0x100u,
101 0.f, // AUDIO_CHANNEL_OUT_SIDE_LEFT = 0x200u,
102 M_SQRT1_2, // AUDIO_CHANNEL_OUT_SIDE_RIGHT = 0x400u,
103 COEF_36, // AUDIO_CHANNEL_OUT_TOP_CENTER = 0x800u,
104 0.f, // AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT = 0x1000u,
105 M_SQRT1_2, // AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER = 0x2000u,
106 1.f, // AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT = 0x4000u,
107 0.f, // AUDIO_CHANNEL_OUT_TOP_BACK_LEFT = 0x8000u,
108 COEF_35, // AUDIO_CHANNEL_OUT_TOP_BACK_CENTER = 0x10000u,
109 M_SQRT1_2, // AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT = 0x20000u,
110 0.f, // AUDIO_CHANNEL_OUT_TOP_SIDE_LEFT = 0x40000u,
111 COEF_61, // AUDIO_CHANNEL_OUT_TOP_SIDE_RIGHT = 0x80000u,
112 0.f, // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_LEFT = 0x100000u,
113 M_SQRT1_2, // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_CENTER = 0x200000u,
114 1.f, // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_RIGHT = 0x400000u,
115 M_SQRT1_2, // AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2 = 0x800000u,
116 0.f, // AUDIO_CHANNEL_OUT_FRONT_WIDE_LEFT = 0x1000000u,
117 M_SQRT1_2, // AUDIO_CHANNEL_OUT_FRONT_WIDE_RIGHT = 0x2000000u,
118 };
119
120 // Downmix doesn't change with sample rate
121 static constexpr size_t kSampleRates[] = {
122 48000,
123 };
124
125 // Our near expectation is 16x the bit that doesn't fit the mantissa.
126 // this works so long as we add values close in exponent with each other
127 // realizing that errors accumulate as the sqrt of N (random walk, lln, etc).
128 #define EXPECT_NEAR_EPSILON(e, v) EXPECT_NEAR((e), (v), \
129 abs((e) * std::numeric_limits<std::decay_t<decltype(e)>>::epsilon() * 8))
130
131 template<typename T>
channelStatistics(const std::vector<T> & input,size_t channels)132 static auto channelStatistics(const std::vector<T>& input, size_t channels) {
133 std::vector<android::audio_utils::Statistics<T>> result(channels);
134 const size_t frames = input.size() / channels;
135 if (frames > 0) {
136 const float *fptr = input.data();
137 for (size_t i = 0; i < frames; ++i) {
138 for (size_t j = 0; j < channels; ++j) {
139 result[j].add(*fptr++);
140 }
141 }
142 }
143 return result;
144 }
145
146 using DownmixParam = std::tuple<int /* sample rate */, int /* channel mask */>;
147 class DownmixTest : public ::testing::TestWithParam<DownmixParam> {
148 public:
149 static constexpr effect_uuid_t downmix_uuid_ = {
150 0x93f04452, 0xe4fe, 0x41cc, 0x91f9, {0xe4, 0x75, 0xb6, 0xd1, 0xd6, 0x9f}};
151 static constexpr size_t FRAME_LENGTH = 256;
152
testBalance(int sampleRate,audio_channel_mask_t channelMask)153 void testBalance(int sampleRate, audio_channel_mask_t channelMask) {
154 using namespace ::android::audio_utils::channels;
155
156 size_t frames = 100; // set to an even number (2, 4, 6 ... ) stream alternates +1, -1.
157 constexpr unsigned outChannels = 2;
158 unsigned inChannels = audio_channel_count_from_out_mask(channelMask);
159 std::vector<float> input(frames * inChannels);
160 std::vector<float> output(frames * outChannels);
161
162 double savedPower[32][2]{};
163 for (unsigned i = 0, channel = channelMask; channel != 0; ++i) {
164 const int index = __builtin_ctz(channel);
165 ASSERT_LT(index, FCC_26);
166 const int pairIndex = pairIdxFromChannelIdx(index);
167 const AUDIO_GEOMETRY_SIDE side = sideFromChannelIdx(index);
168 const int channelBit = 1 << index;
169 channel &= ~channelBit;
170
171 // Generate a +1, -1 alternating stream in one channel, which has variance 1.
172 auto indata = input.data();
173 for (unsigned j = 0; j < frames; ++j) {
174 for (unsigned k = 0; k < inChannels; ++k) {
175 *indata++ = (k == i) ? (j & 1 ? -1 : 1) : 0;
176 }
177 }
178 run(sampleRate, channelMask, input, output, frames);
179
180 auto stats = channelStatistics(output, 2 /* channels */);
181 // printf("power: %s %s\n", stats[0].toString().c_str(), stats[1].toString().c_str());
182 double power[2] = { stats[0].getPopVariance(), stats[1].getPopVariance() };
183
184 // Check symmetric power for pair channels on exchange of left/right position.
185 // to do this, we save previous power measurements.
186 if (pairIndex >= 0 && pairIndex < index) {
187 EXPECT_NEAR_EPSILON(power[0], savedPower[pairIndex][1]);
188 EXPECT_NEAR_EPSILON(power[1], savedPower[pairIndex][0]);
189 }
190 savedPower[index][0] = power[0];
191 savedPower[index][1] = power[1];
192
193 constexpr float POWER_TOLERANCE = 0.001;
194 const float expectedPower =
195 kScaleFromChannelIdxLeft[index] * kScaleFromChannelIdxLeft[index]
196 + kScaleFromChannelIdxRight[index] * kScaleFromChannelIdxRight[index];
197 EXPECT_NEAR(expectedPower, power[0] + power[1], POWER_TOLERANCE);
198 switch (side) {
199 case AUDIO_GEOMETRY_SIDE_LEFT:
200 if (channelBit == AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER) {
201 break;
202 }
203 EXPECT_EQ(0.f, power[1]);
204 break;
205 case AUDIO_GEOMETRY_SIDE_RIGHT:
206 if (channelBit == AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER) {
207 break;
208 }
209 EXPECT_EQ(0.f, power[0]);
210 break;
211 case AUDIO_GEOMETRY_SIDE_CENTER:
212 if (channelBit == AUDIO_CHANNEL_OUT_LOW_FREQUENCY) {
213 if (channelMask & AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2) {
214 EXPECT_EQ(0.f, power[1]);
215 break;
216 } else {
217 EXPECT_NEAR_EPSILON(power[0], power[1]); // always true
218 EXPECT_NEAR(expectedPower, power[0] + power[1], POWER_TOLERANCE);
219 break;
220 }
221 } else if (channelBit == AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2) {
222 EXPECT_EQ(0.f, power[0]);
223 EXPECT_NEAR(expectedPower, power[1], POWER_TOLERANCE);
224 break;
225 }
226 EXPECT_NEAR_EPSILON(power[0], power[1]);
227 break;
228 }
229 }
230 }
231
run(int sampleRate,audio_channel_mask_t channelMask,std::vector<float> & input,std::vector<float> & output,size_t frames)232 void run(int sampleRate, audio_channel_mask_t channelMask,
233 std::vector<float>& input, std::vector<float>& output, size_t frames) {
234 reconfig(sampleRate, channelMask);
235
236 ASSERT_EQ(frames * inputChannelCount_, input.size());
237 ASSERT_EQ(frames * outputChannelCount_, output.size());
238
239 const int32_t sessionId = 0;
240 const int32_t ioId = 0;
241 int32_t err = AUDIO_EFFECT_LIBRARY_INFO_SYM.create_effect(
242 &downmix_uuid_, sessionId, ioId, &handle_);
243 ASSERT_EQ(0, err);
244
245 const struct effect_interface_s * const downmixApi = *handle_;
246 int32_t reply = 0;
247 uint32_t replySize = (uint32_t)sizeof(reply);
248 err = (downmixApi->command)(
249 handle_, EFFECT_CMD_SET_CONFIG,
250 sizeof(effect_config_t), &config_, &replySize, &reply);
251 ASSERT_EQ(0, err);
252 ASSERT_EQ(0, reply);
253 err = (downmixApi->command)(
254 handle_, EFFECT_CMD_ENABLE,
255 0, nullptr, &replySize, &reply);
256 ASSERT_EQ(0, err);
257
258 process(input, output, frames);
259 err = AUDIO_EFFECT_LIBRARY_INFO_SYM.release_effect(handle_);
260 ASSERT_EQ(0, err);
261 }
262
263 // This test assumes the channel mask is invalid.
testInvalidChannelMask(audio_channel_mask_t invalidChannelMask)264 void testInvalidChannelMask(audio_channel_mask_t invalidChannelMask) {
265 reconfig(48000 /* sampleRate */, invalidChannelMask);
266 const int32_t sessionId = 0;
267 const int32_t ioId = 0;
268 int32_t err = AUDIO_EFFECT_LIBRARY_INFO_SYM.create_effect(
269 &downmix_uuid_, sessionId, ioId, &handle_);
270 ASSERT_EQ(0, err);
271
272 const struct effect_interface_s * const downmixApi = *handle_;
273 int32_t reply = 0;
274 uint32_t replySize = (uint32_t)sizeof(reply);
275 err = (downmixApi->command)(
276 handle_, EFFECT_CMD_SET_CONFIG,
277 sizeof(effect_config_t), &config_, &replySize, &reply);
278 ASSERT_EQ(0, err);
279 ASSERT_NE(0, reply); // error has occurred.
280 err = AUDIO_EFFECT_LIBRARY_INFO_SYM.release_effect(handle_);
281 ASSERT_EQ(0, err);
282 }
283
284 private:
reconfig(int sampleRate,audio_channel_mask_t channelMask)285 void reconfig(int sampleRate, audio_channel_mask_t channelMask) {
286 config_.inputCfg.accessMode = EFFECT_BUFFER_ACCESS_READ;
287 config_.inputCfg.format = AUDIO_FORMAT_PCM_FLOAT;
288 config_.inputCfg.bufferProvider.getBuffer = nullptr;
289 config_.inputCfg.bufferProvider.releaseBuffer = nullptr;
290 config_.inputCfg.bufferProvider.cookie = nullptr;
291 config_.inputCfg.mask = EFFECT_CONFIG_ALL;
292
293 config_.outputCfg.accessMode = EFFECT_BUFFER_ACCESS_WRITE;
294 config_.outputCfg.format = AUDIO_FORMAT_PCM_FLOAT;
295 config_.outputCfg.bufferProvider.getBuffer = nullptr;
296 config_.outputCfg.bufferProvider.releaseBuffer = nullptr;
297 config_.outputCfg.bufferProvider.cookie = nullptr;
298 config_.outputCfg.mask = EFFECT_CONFIG_ALL;
299
300 config_.inputCfg.samplingRate = sampleRate;
301 config_.inputCfg.channels = channelMask;
302 inputChannelCount_ = audio_channel_count_from_out_mask(config_.inputCfg.channels);
303
304 config_.outputCfg.samplingRate = sampleRate;
305 config_.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO; // output always stereo
306 outputChannelCount_ = audio_channel_count_from_out_mask(config_.outputCfg.channels);
307 }
308
process(std::vector<float> & input,std::vector<float> & output,size_t frames) const309 void process(std::vector<float> &input, std::vector<float> &output, size_t frames) const {
310 const struct effect_interface_s * const downmixApi = *handle_;
311
312 for (size_t pos = 0; pos < frames;) {
313 const size_t transfer = std::min(frames - pos, FRAME_LENGTH);
314 audio_buffer_t inbuffer{.frameCount = transfer,
315 .f32 = input.data() + pos * inputChannelCount_};
316 audio_buffer_t outbuffer{.frameCount = transfer,
317 .f32 = output.data() + pos * outputChannelCount_};
318 const int32_t err = (downmixApi->process)(handle_, &inbuffer, &outbuffer);
319 ASSERT_EQ(0, err);
320 pos += transfer;
321 }
322 }
323
324 effect_handle_t handle_{};
325 effect_config_t config_{};
326 int outputChannelCount_{};
327 int inputChannelCount_{};
328 };
329
TEST(DownmixTestSimple,invalidChannelMask)330 TEST(DownmixTestSimple, invalidChannelMask) {
331 // Fill in a dummy test method to use DownmixTest outside of a parameterized test.
332 class DownmixTestComplete : public DownmixTest {
333 void TestBody() override {}
334 } downmixtest;
335
336 constexpr auto INVALID_CHANNEL_MASK = audio_channel_mask_t(1 << 31);
337 downmixtest.testInvalidChannelMask(INVALID_CHANNEL_MASK);
338 }
339
TEST_P(DownmixTest,basic)340 TEST_P(DownmixTest, basic) {
341 testBalance(kSampleRates[std::get<0>(GetParam())],
342 kChannelPositionMasks[std::get<1>(GetParam())]);
343 }
344
345 INSTANTIATE_TEST_SUITE_P(
346 DownmixTestAll, DownmixTest,
347 ::testing::Combine(
348 ::testing::Range(0, (int)std::size(kSampleRates)),
349 ::testing::Range(0, (int)std::size(kChannelPositionMasks))
350 ),
__anonf1639df60102(const testing::TestParamInfo<DownmixTest::ParamType>& info) 351 [](const testing::TestParamInfo<DownmixTest::ParamType>& info) {
352 const int index = std::get<1>(info.param);
353 const audio_channel_mask_t channelMask = kChannelPositionMasks[index];
354 const std::string name = std::string(audio_channel_out_mask_to_string(channelMask))
355 + "_" + std::to_string(std::get<0>(info.param)) + "_" + std::to_string(index);
356 return name;
357 });
358