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,
37 AUDIO_CHANNEL_OUT_QUAD_BACK,
38 AUDIO_CHANNEL_OUT_QUAD_SIDE,
39 AUDIO_CHANNEL_OUT_SURROUND,
40 AUDIO_CHANNEL_OUT_2POINT1POINT2,
41 AUDIO_CHANNEL_OUT_3POINT0POINT2,
42 AUDIO_CHANNEL_OUT_PENTA,
43 AUDIO_CHANNEL_OUT_3POINT1POINT2,
44 AUDIO_CHANNEL_OUT_5POINT1,
45 AUDIO_CHANNEL_OUT_5POINT1_BACK,
46 AUDIO_CHANNEL_OUT_5POINT1_SIDE,
47 AUDIO_CHANNEL_OUT_6POINT1,
48 AUDIO_CHANNEL_OUT_5POINT1POINT2,
49 AUDIO_CHANNEL_OUT_7POINT1,
50 AUDIO_CHANNEL_OUT_5POINT1POINT4,
51 AUDIO_CHANNEL_OUT_7POINT1POINT2,
52 AUDIO_CHANNEL_OUT_7POINT1POINT4,
53 AUDIO_CHANNEL_OUT_13POINT_360RA,
54 AUDIO_CHANNEL_OUT_22POINT2,
55 };
56
57 static constexpr audio_channel_mask_t kConsideredChannels =
58 (audio_channel_mask_t)(AUDIO_CHANNEL_OUT_7POINT1 | AUDIO_CHANNEL_OUT_BACK_CENTER);
59
60 // Downmix doesn't change with sample rate
61 static constexpr size_t kSampleRates[] = {
62 48000,
63 };
64
65 // Our near expectation is 16x the bit that doesn't fit the mantissa.
66 // this works so long as we add values close in exponent with each other
67 // realizing that errors accumulate as the sqrt of N (random walk, lln, etc).
68 #define EXPECT_NEAR_EPSILON(e, v) EXPECT_NEAR((e), (v), \
69 abs((e) * std::numeric_limits<std::decay_t<decltype(e)>>::epsilon() * 8))
70
71 template<typename T>
channelStatistics(const std::vector<T> & input,size_t channels)72 static auto channelStatistics(const std::vector<T>& input, size_t channels) {
73 std::vector<android::audio_utils::Statistics<T>> result(channels);
74 const size_t frames = input.size() / channels;
75 if (frames > 0) {
76 const float *fptr = input.data();
77 for (size_t i = 0; i < frames; ++i) {
78 for (size_t j = 0; j < channels; ++j) {
79 result[j].add(*fptr++);
80 }
81 }
82 }
83 return result;
84 }
85
86 using DownmixParam = std::tuple<int /* sample rate */, int /* channel mask */>;
87 class DownmixTest : public ::testing::TestWithParam<DownmixParam> {
88 public:
89 static constexpr effect_uuid_t downmix_uuid_ = {
90 0x93f04452, 0xe4fe, 0x41cc, 0x91f9, {0xe4, 0x75, 0xb6, 0xd1, 0xd6, 0x9f}};
91 static constexpr size_t FRAME_LENGTH = 256;
92
testBalance(int sampleRate,audio_channel_mask_t channelMask)93 void testBalance(int sampleRate, audio_channel_mask_t channelMask) {
94 using namespace ::android::audio_utils::channels;
95
96 size_t frames = 100;
97 unsigned outChannels = 2;
98 unsigned inChannels = audio_channel_count_from_out_mask(channelMask);
99 std::vector<float> input(frames * inChannels);
100 std::vector<float> output(frames * outChannels);
101
102 double savedPower[32][2]{};
103 for (unsigned i = 0, channel = channelMask; channel != 0; ++i) {
104 const int index = __builtin_ctz(channel);
105 ASSERT_LT(index, FCC_24);
106 const int pairIndex = pairIdxFromChannelIdx(index);
107 const AUDIO_GEOMETRY_SIDE side = sideFromChannelIdx(index);
108 const int channelBit = 1 << index;
109 channel &= ~channelBit;
110
111 // Generate a +1, -1 alternating stream in one channel, which has variance 1.
112 auto indata = input.data();
113 for (unsigned j = 0; j < frames; ++j) {
114 for (unsigned k = 0; k < inChannels; ++k) {
115 *indata++ = (k == i) ? (j & 1 ? -1 : 1) : 0;
116 }
117 }
118 run(sampleRate, channelMask, input, output, frames);
119
120 auto stats = channelStatistics(output, 2 /* channels */);
121 // printf("power: %s %s\n", stats[0].toString().c_str(), stats[1].toString().c_str());
122 double power[2] = { stats[0].getVariance(), stats[1].getVariance() };
123
124 // Check symmetric power for pair channels on exchange of left/right position.
125 // to do this, we save previous power measurements.
126 if (pairIndex >= 0 && pairIndex < index) {
127 EXPECT_NEAR_EPSILON(power[0], savedPower[pairIndex][1]);
128 EXPECT_NEAR_EPSILON(power[1], savedPower[pairIndex][0]);
129 }
130 savedPower[index][0] = power[0];
131 savedPower[index][1] = power[1];
132
133 // Confirm exactly the mix amount prescribed by the existing downmix effect.
134 // For future changes to the downmix effect, the nearness needs to be relaxed
135 // to compare behavior S or earlier.
136 if ((channelBit & kConsideredChannels) == 0) {
137 // for channels not considered, expect 0 power for legacy downmix
138 EXPECT_EQ(0.f, power[0]);
139 EXPECT_EQ(0.f, power[1]);
140 continue;
141 }
142 constexpr float POWER_TOLERANCE = 0.01; // for variance sum error.
143 switch (side) {
144 case AUDIO_GEOMETRY_SIDE_LEFT:
145 EXPECT_NEAR(0.25f, power[0], POWER_TOLERANCE);
146 EXPECT_EQ(0.f, power[1]);
147 break;
148 case AUDIO_GEOMETRY_SIDE_RIGHT:
149 EXPECT_EQ(0.f, power[0]);
150 EXPECT_NEAR(0.25f, power[1], POWER_TOLERANCE);
151 break;
152 case AUDIO_GEOMETRY_SIDE_CENTER:
153 EXPECT_NEAR(0.125f, power[0], POWER_TOLERANCE);
154 EXPECT_NEAR(0.125f, power[1], POWER_TOLERANCE);
155 EXPECT_NEAR_EPSILON(power[0], power[1]);
156 break;
157 }
158 }
159 }
160
run(int sampleRate,audio_channel_mask_t channelMask,std::vector<float> & input,std::vector<float> & output,size_t frames)161 void run(int sampleRate, audio_channel_mask_t channelMask,
162 std::vector<float>& input, std::vector<float>& output, size_t frames) {
163 reconfig(sampleRate, channelMask);
164
165 ASSERT_EQ(frames * inputChannelCount_, input.size());
166 ASSERT_EQ(frames * outputChannelCount_, output.size());
167
168 const int32_t sessionId = 0;
169 const int32_t ioId = 0;
170 int32_t err = AUDIO_EFFECT_LIBRARY_INFO_SYM.create_effect(
171 &downmix_uuid_, sessionId, ioId, &handle_);
172 ASSERT_EQ(0, err);
173
174 const struct effect_interface_s * const downmixApi = *handle_;
175 int32_t reply = 0;
176 uint32_t replySize = (uint32_t)sizeof(reply);
177 err = (downmixApi->command)(
178 handle_, EFFECT_CMD_SET_CONFIG,
179 sizeof(effect_config_t), &config_, &replySize, &reply);
180 ASSERT_EQ(0, err);
181 err = (downmixApi->command)(
182 handle_, EFFECT_CMD_ENABLE,
183 0, nullptr, &replySize, &reply);
184 ASSERT_EQ(0, err);
185
186 process(input, output, frames);
187 err = AUDIO_EFFECT_LIBRARY_INFO_SYM.release_effect(handle_);
188 ASSERT_EQ(0, err);
189 }
190
191 private:
reconfig(int sampleRate,audio_channel_mask_t channelMask)192 void reconfig(int sampleRate, audio_channel_mask_t channelMask) {
193 config_.inputCfg.accessMode = EFFECT_BUFFER_ACCESS_READ;
194 config_.inputCfg.format = AUDIO_FORMAT_PCM_FLOAT;
195 config_.inputCfg.bufferProvider.getBuffer = nullptr;
196 config_.inputCfg.bufferProvider.releaseBuffer = nullptr;
197 config_.inputCfg.bufferProvider.cookie = nullptr;
198 config_.inputCfg.mask = EFFECT_CONFIG_ALL;
199
200 config_.outputCfg.accessMode = EFFECT_BUFFER_ACCESS_WRITE;
201 config_.outputCfg.format = AUDIO_FORMAT_PCM_FLOAT;
202 config_.outputCfg.bufferProvider.getBuffer = nullptr;
203 config_.outputCfg.bufferProvider.releaseBuffer = nullptr;
204 config_.outputCfg.bufferProvider.cookie = nullptr;
205 config_.outputCfg.mask = EFFECT_CONFIG_ALL;
206
207 config_.inputCfg.samplingRate = sampleRate;
208 config_.inputCfg.channels = channelMask;
209 inputChannelCount_ = audio_channel_count_from_out_mask(config_.inputCfg.channels);
210
211 config_.outputCfg.samplingRate = sampleRate;
212 config_.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO; // output always stereo
213 outputChannelCount_ = audio_channel_count_from_out_mask(config_.outputCfg.channels);
214 }
215
process(std::vector<float> & input,std::vector<float> & output,size_t frames) const216 void process(std::vector<float> &input, std::vector<float> &output, size_t frames) const {
217 const struct effect_interface_s * const downmixApi = *handle_;
218
219 for (size_t pos = 0; pos < frames;) {
220 const size_t transfer = std::min(frames - pos, FRAME_LENGTH);
221 audio_buffer_t inbuffer{.frameCount = transfer,
222 .f32 = input.data() + pos * inputChannelCount_};
223 audio_buffer_t outbuffer{.frameCount = transfer,
224 .f32 = output.data() + pos * outputChannelCount_};
225 const int32_t err = (downmixApi->process)(handle_, &inbuffer, &outbuffer);
226 ASSERT_EQ(0, err);
227 pos += transfer;
228 }
229 }
230
231 effect_handle_t handle_{};
232 effect_config_t config_{};
233 int outputChannelCount_{};
234 int inputChannelCount_{};
235 };
236
TEST_P(DownmixTest,basic)237 TEST_P(DownmixTest, basic) {
238 testBalance(kSampleRates[std::get<0>(GetParam())],
239 kChannelPositionMasks[std::get<1>(GetParam())]);
240 }
241
242 INSTANTIATE_TEST_SUITE_P(
243 DownmixTestAll, DownmixTest,
244 ::testing::Combine(
245 ::testing::Range(0, (int)std::size(kSampleRates)),
246 ::testing::Range(0, (int)std::size(kChannelPositionMasks))
247 ));
248
main(int argc,char ** argv)249 int main(int argc, /* const */ char** argv) {
250 ::testing::InitGoogleTest(&argc, argv);
251 int status = RUN_ALL_TESTS();
252 return status;
253 }
254