• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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