xref: /aosp_15_r20/system/media/audio_utils/tests/channelmix_tests.cpp (revision b9df5ad1c9ac98a7fefaac271a55f7ae3db05414)
1*b9df5ad1SAndroid Build Coastguard Worker /*
2*b9df5ad1SAndroid Build Coastguard Worker  * Copyright (C) 2021 The Android Open Source Project
3*b9df5ad1SAndroid Build Coastguard Worker  *
4*b9df5ad1SAndroid Build Coastguard Worker  * Licensed under the Apache License, Version 2.0 (the "License");
5*b9df5ad1SAndroid Build Coastguard Worker  * you may not use this file except in compliance with the License.
6*b9df5ad1SAndroid Build Coastguard Worker  * You may obtain a copy of the License at
7*b9df5ad1SAndroid Build Coastguard Worker  *
8*b9df5ad1SAndroid Build Coastguard Worker  *      http://www.apache.org/licenses/LICENSE-2.0
9*b9df5ad1SAndroid Build Coastguard Worker  *
10*b9df5ad1SAndroid Build Coastguard Worker  * Unless required by applicable law or agreed to in writing, software
11*b9df5ad1SAndroid Build Coastguard Worker  * distributed under the License is distributed on an "AS IS" BASIS,
12*b9df5ad1SAndroid Build Coastguard Worker  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*b9df5ad1SAndroid Build Coastguard Worker  * See the License for the specific language governing permissions and
14*b9df5ad1SAndroid Build Coastguard Worker  * limitations under the License.
15*b9df5ad1SAndroid Build Coastguard Worker  */
16*b9df5ad1SAndroid Build Coastguard Worker 
17*b9df5ad1SAndroid Build Coastguard Worker #include <audio_utils/ChannelMix.h>
18*b9df5ad1SAndroid Build Coastguard Worker #include <audio_utils/Statistics.h>
19*b9df5ad1SAndroid Build Coastguard Worker #include <gtest/gtest.h>
20*b9df5ad1SAndroid Build Coastguard Worker #include <log/log.h>
21*b9df5ad1SAndroid Build Coastguard Worker 
22*b9df5ad1SAndroid Build Coastguard Worker static constexpr audio_channel_mask_t kOutputChannelMasks[] = {
23*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_STEREO,
24*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_5POINT1, // AUDIO_CHANNEL_OUT_5POINT1_BACK
25*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_7POINT1,
26*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_7POINT1POINT4,
27*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_9POINT1POINT6,
28*b9df5ad1SAndroid Build Coastguard Worker };
29*b9df5ad1SAndroid Build Coastguard Worker 
30*b9df5ad1SAndroid Build Coastguard Worker static constexpr audio_channel_mask_t kInputChannelMasks[] = {
31*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_FRONT_LEFT, // Legacy: the ChannelMix effect treats MONO as FRONT_LEFT only.
32*b9df5ad1SAndroid Build Coastguard Worker                                   // The AudioMixer interprets MONO as a special case requiring
33*b9df5ad1SAndroid Build Coastguard Worker                                   // channel replication, bypassing the ChannelMix effect.
34*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_FRONT_CENTER,
35*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_STEREO,
36*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_2POINT1,
37*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_2POINT0POINT2,
38*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_QUAD, // AUDIO_CHANNEL_OUT_QUAD_BACK
39*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_QUAD_SIDE,
40*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_SURROUND,
41*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_2POINT1POINT2,
42*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_3POINT0POINT2,
43*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_PENTA,
44*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_3POINT1POINT2,
45*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_5POINT1, // AUDIO_CHANNEL_OUT_5POINT1_BACK
46*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_5POINT1_SIDE,
47*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_6POINT1,
48*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_5POINT1POINT2,
49*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_7POINT1,
50*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_5POINT1POINT4,
51*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_7POINT1POINT2,
52*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_7POINT1POINT4,
53*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_9POINT1POINT6,
54*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_13POINT_360RA,
55*b9df5ad1SAndroid Build Coastguard Worker     AUDIO_CHANNEL_OUT_22POINT2,
56*b9df5ad1SAndroid Build Coastguard Worker     audio_channel_mask_t(AUDIO_CHANNEL_OUT_22POINT2
57*b9df5ad1SAndroid Build Coastguard Worker             | AUDIO_CHANNEL_OUT_FRONT_WIDE_LEFT | AUDIO_CHANNEL_OUT_FRONT_WIDE_RIGHT),
58*b9df5ad1SAndroid Build Coastguard Worker };
59*b9df5ad1SAndroid Build Coastguard Worker 
60*b9df5ad1SAndroid Build Coastguard Worker constexpr float COEF_25 = 0.2508909536f;
61*b9df5ad1SAndroid Build Coastguard Worker constexpr float COEF_35 = 0.3543928915f;
62*b9df5ad1SAndroid Build Coastguard Worker constexpr float COEF_36 = 0.3552343859f;
63*b9df5ad1SAndroid Build Coastguard Worker constexpr float COEF_61 = 0.6057043428f;
64*b9df5ad1SAndroid Build Coastguard Worker 
65*b9df5ad1SAndroid Build Coastguard Worker constexpr inline float kScaleFromChannelIdxLeft[] = {
66*b9df5ad1SAndroid Build Coastguard Worker     1.f,       // AUDIO_CHANNEL_OUT_FRONT_LEFT            = 0x1u,
67*b9df5ad1SAndroid Build Coastguard Worker     0.f,       // AUDIO_CHANNEL_OUT_FRONT_RIGHT           = 0x2u,
68*b9df5ad1SAndroid Build Coastguard Worker     M_SQRT1_2, // AUDIO_CHANNEL_OUT_FRONT_CENTER          = 0x4u,
69*b9df5ad1SAndroid Build Coastguard Worker     0.5f,      // AUDIO_CHANNEL_OUT_LOW_FREQUENCY         = 0x8u,
70*b9df5ad1SAndroid Build Coastguard Worker     M_SQRT1_2, // AUDIO_CHANNEL_OUT_BACK_LEFT             = 0x10u,
71*b9df5ad1SAndroid Build Coastguard Worker     0.f,       // AUDIO_CHANNEL_OUT_BACK_RIGHT            = 0x20u,
72*b9df5ad1SAndroid Build Coastguard Worker     COEF_61,   // AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER  = 0x40u,
73*b9df5ad1SAndroid Build Coastguard Worker     COEF_25,   // AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x80u,
74*b9df5ad1SAndroid Build Coastguard Worker     0.5f,      // AUDIO_CHANNEL_OUT_BACK_CENTER           = 0x100u,
75*b9df5ad1SAndroid Build Coastguard Worker     M_SQRT1_2, // AUDIO_CHANNEL_OUT_SIDE_LEFT             = 0x200u,
76*b9df5ad1SAndroid Build Coastguard Worker     0.f,       // AUDIO_CHANNEL_OUT_SIDE_RIGHT            = 0x400u,
77*b9df5ad1SAndroid Build Coastguard Worker     COEF_36,   // AUDIO_CHANNEL_OUT_TOP_CENTER            = 0x800u,
78*b9df5ad1SAndroid Build Coastguard Worker     1.f,       // AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT        = 0x1000u,
79*b9df5ad1SAndroid Build Coastguard Worker     M_SQRT1_2, // AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER      = 0x2000u,
80*b9df5ad1SAndroid Build Coastguard Worker     0.f,       // AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT       = 0x4000u,
81*b9df5ad1SAndroid Build Coastguard Worker     M_SQRT1_2, // AUDIO_CHANNEL_OUT_TOP_BACK_LEFT         = 0x8000u,
82*b9df5ad1SAndroid Build Coastguard Worker     COEF_35,   // AUDIO_CHANNEL_OUT_TOP_BACK_CENTER       = 0x10000u,
83*b9df5ad1SAndroid Build Coastguard Worker     0.f,       // AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT        = 0x20000u,
84*b9df5ad1SAndroid Build Coastguard Worker     COEF_61,   // AUDIO_CHANNEL_OUT_TOP_SIDE_LEFT         = 0x40000u,
85*b9df5ad1SAndroid Build Coastguard Worker     0.f,       // AUDIO_CHANNEL_OUT_TOP_SIDE_RIGHT        = 0x80000u,
86*b9df5ad1SAndroid Build Coastguard Worker     1.f,       // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_LEFT     = 0x100000u,
87*b9df5ad1SAndroid Build Coastguard Worker     M_SQRT1_2, // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_CENTER   = 0x200000u,
88*b9df5ad1SAndroid Build Coastguard Worker     0.f,       // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_RIGHT    = 0x400000u,
89*b9df5ad1SAndroid Build Coastguard Worker     0.f,       // AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2       = 0x800000u,
90*b9df5ad1SAndroid Build Coastguard Worker     M_SQRT1_2, // AUDIO_CHANNEL_OUT_FRONT_WIDE_LEFT       = 0x1000000u,
91*b9df5ad1SAndroid Build Coastguard Worker     0.f,       // AUDIO_CHANNEL_OUT_FRONT_WIDE_RIGHT      = 0x2000000u,
92*b9df5ad1SAndroid Build Coastguard Worker };
93*b9df5ad1SAndroid Build Coastguard Worker 
94*b9df5ad1SAndroid Build Coastguard Worker constexpr inline float kScaleFromChannelIdxRight[] = {
95*b9df5ad1SAndroid Build Coastguard Worker     0.f,       // AUDIO_CHANNEL_OUT_FRONT_LEFT            = 0x1u,
96*b9df5ad1SAndroid Build Coastguard Worker     1.f,       // AUDIO_CHANNEL_OUT_FRONT_RIGHT           = 0x2u,
97*b9df5ad1SAndroid Build Coastguard Worker     M_SQRT1_2, // AUDIO_CHANNEL_OUT_FRONT_CENTER          = 0x4u,
98*b9df5ad1SAndroid Build Coastguard Worker     0.5f,      // AUDIO_CHANNEL_OUT_LOW_FREQUENCY         = 0x8u,
99*b9df5ad1SAndroid Build Coastguard Worker     0.f,       // AUDIO_CHANNEL_OUT_BACK_LEFT             = 0x10u,
100*b9df5ad1SAndroid Build Coastguard Worker     M_SQRT1_2, // AUDIO_CHANNEL_OUT_BACK_RIGHT            = 0x20u,
101*b9df5ad1SAndroid Build Coastguard Worker     COEF_25,   // AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER  = 0x40u,
102*b9df5ad1SAndroid Build Coastguard Worker     COEF_61,   // AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x80u,
103*b9df5ad1SAndroid Build Coastguard Worker     0.5f,      // AUDIO_CHANNEL_OUT_BACK_CENTER           = 0x100u,
104*b9df5ad1SAndroid Build Coastguard Worker     0.f,       // AUDIO_CHANNEL_OUT_SIDE_LEFT             = 0x200u,
105*b9df5ad1SAndroid Build Coastguard Worker     M_SQRT1_2, // AUDIO_CHANNEL_OUT_SIDE_RIGHT            = 0x400u,
106*b9df5ad1SAndroid Build Coastguard Worker     COEF_36,   // AUDIO_CHANNEL_OUT_TOP_CENTER            = 0x800u,
107*b9df5ad1SAndroid Build Coastguard Worker     0.f,       // AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT        = 0x1000u,
108*b9df5ad1SAndroid Build Coastguard Worker     M_SQRT1_2, // AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER      = 0x2000u,
109*b9df5ad1SAndroid Build Coastguard Worker     1.f,       // AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT       = 0x4000u,
110*b9df5ad1SAndroid Build Coastguard Worker     0.f,       // AUDIO_CHANNEL_OUT_TOP_BACK_LEFT         = 0x8000u,
111*b9df5ad1SAndroid Build Coastguard Worker     COEF_35,   // AUDIO_CHANNEL_OUT_TOP_BACK_CENTER       = 0x10000u,
112*b9df5ad1SAndroid Build Coastguard Worker     M_SQRT1_2, // AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT        = 0x20000u,
113*b9df5ad1SAndroid Build Coastguard Worker     0.f,       // AUDIO_CHANNEL_OUT_TOP_SIDE_LEFT         = 0x40000u,
114*b9df5ad1SAndroid Build Coastguard Worker     COEF_61,   // AUDIO_CHANNEL_OUT_TOP_SIDE_RIGHT        = 0x80000u,
115*b9df5ad1SAndroid Build Coastguard Worker     0.f,       // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_LEFT     = 0x100000u,
116*b9df5ad1SAndroid Build Coastguard Worker     M_SQRT1_2, // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_CENTER   = 0x200000u,
117*b9df5ad1SAndroid Build Coastguard Worker     1.f,       // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_RIGHT    = 0x400000u,
118*b9df5ad1SAndroid Build Coastguard Worker     M_SQRT1_2, // AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2       = 0x800000u,
119*b9df5ad1SAndroid Build Coastguard Worker     0.f,       // AUDIO_CHANNEL_OUT_FRONT_WIDE_LEFT       = 0x1000000u,
120*b9df5ad1SAndroid Build Coastguard Worker     M_SQRT1_2, // AUDIO_CHANNEL_OUT_FRONT_WIDE_RIGHT      = 0x2000000u,
121*b9df5ad1SAndroid Build Coastguard Worker };
122*b9df5ad1SAndroid Build Coastguard Worker 
123*b9df5ad1SAndroid Build Coastguard Worker // Our near expectation is 16x the bit that doesn't fit the mantissa.
124*b9df5ad1SAndroid Build Coastguard Worker // this works so long as we add values close in exponent with each other
125*b9df5ad1SAndroid Build Coastguard Worker // realizing that errors accumulate as the sqrt of N (random walk, lln, etc).
126*b9df5ad1SAndroid Build Coastguard Worker #define EXPECT_NEAR_EPSILON(e, v) EXPECT_NEAR((e), (v), \
127*b9df5ad1SAndroid Build Coastguard Worker         abs((e) * std::numeric_limits<std::decay_t<decltype(e)>>::epsilon() * 8))
128*b9df5ad1SAndroid Build Coastguard Worker 
129*b9df5ad1SAndroid Build Coastguard Worker template<typename T>
channelStatistics(const std::vector<T> & input,size_t channels)130*b9df5ad1SAndroid Build Coastguard Worker static auto channelStatistics(const std::vector<T>& input, size_t channels) {
131*b9df5ad1SAndroid Build Coastguard Worker     std::vector<android::audio_utils::Statistics<T>> result(channels);
132*b9df5ad1SAndroid Build Coastguard Worker     const size_t frames = input.size() / channels;
133*b9df5ad1SAndroid Build Coastguard Worker     if (frames > 0) {
134*b9df5ad1SAndroid Build Coastguard Worker         const float *fptr = input.data();
135*b9df5ad1SAndroid Build Coastguard Worker         for (size_t i = 0; i < frames; ++i) {
136*b9df5ad1SAndroid Build Coastguard Worker             for (size_t j = 0; j < channels; ++j) {
137*b9df5ad1SAndroid Build Coastguard Worker                 result[j].add(*fptr++);
138*b9df5ad1SAndroid Build Coastguard Worker             }
139*b9df5ad1SAndroid Build Coastguard Worker         }
140*b9df5ad1SAndroid Build Coastguard Worker     }
141*b9df5ad1SAndroid Build Coastguard Worker     return result;
142*b9df5ad1SAndroid Build Coastguard Worker }
143*b9df5ad1SAndroid Build Coastguard Worker 
144*b9df5ad1SAndroid Build Coastguard Worker using ChannelMixParam = std::tuple<int /* output channel mask */,
145*b9df5ad1SAndroid Build Coastguard Worker         int /* input channel mask */,
146*b9df5ad1SAndroid Build Coastguard Worker         bool /* accumulate */>;
147*b9df5ad1SAndroid Build Coastguard Worker 
148*b9df5ad1SAndroid Build Coastguard Worker // For ChannelMixParam tuple get.
149*b9df5ad1SAndroid Build Coastguard Worker constexpr size_t OUTPUT_CHANNEL_MASK_POSITION = 0;
150*b9df5ad1SAndroid Build Coastguard Worker constexpr size_t INPUT_CHANNEL_MASK_POSITION = 1;
151*b9df5ad1SAndroid Build Coastguard Worker constexpr size_t ACCUMULATE_POSITION = 2;
152*b9df5ad1SAndroid Build Coastguard Worker 
153*b9df5ad1SAndroid Build Coastguard Worker class ChannelMixTest : public ::testing::TestWithParam<ChannelMixParam> {
154*b9df5ad1SAndroid Build Coastguard Worker public:
155*b9df5ad1SAndroid Build Coastguard Worker 
testBalance(audio_channel_mask_t outputChannelMask,audio_channel_mask_t inputChannelMask,bool accumulate)156*b9df5ad1SAndroid Build Coastguard Worker     void testBalance(audio_channel_mask_t outputChannelMask,
157*b9df5ad1SAndroid Build Coastguard Worker             audio_channel_mask_t inputChannelMask, bool accumulate) {
158*b9df5ad1SAndroid Build Coastguard Worker         using namespace ::android::audio_utils::channels;
159*b9df5ad1SAndroid Build Coastguard Worker 
160*b9df5ad1SAndroid Build Coastguard Worker         size_t frames = 100; // set to an even number (2, 4, 6 ... ) stream alternates +1, -1.
161*b9df5ad1SAndroid Build Coastguard Worker         const unsigned outChannels = audio_channel_count_from_out_mask(outputChannelMask);
162*b9df5ad1SAndroid Build Coastguard Worker         const unsigned inChannels = audio_channel_count_from_out_mask(inputChannelMask);
163*b9df5ad1SAndroid Build Coastguard Worker         std::vector<float> input(frames * inChannels);
164*b9df5ad1SAndroid Build Coastguard Worker         std::vector<float> output(frames * outChannels);
165*b9df5ad1SAndroid Build Coastguard Worker 
166*b9df5ad1SAndroid Build Coastguard Worker         double savedPower[32 /* inChannels */][32 /* outChannels */]{};
167*b9df5ad1SAndroid Build Coastguard Worker 
168*b9df5ad1SAndroid Build Coastguard Worker         // Precompute output channel geometry.
169*b9df5ad1SAndroid Build Coastguard Worker         AUDIO_GEOMETRY_SIDE outSide[outChannels];  // what side that channel index is on
170*b9df5ad1SAndroid Build Coastguard Worker         int outIndexToOffset[32] = {[0 ... 31] = -1};
171*b9df5ad1SAndroid Build Coastguard Worker         int outPair[outChannels];  // is there a matching pair channel?
172*b9df5ad1SAndroid Build Coastguard Worker         for (unsigned i = 0, channel = outputChannelMask; channel != 0; ++i) {
173*b9df5ad1SAndroid Build Coastguard Worker             const int index = __builtin_ctz(channel);
174*b9df5ad1SAndroid Build Coastguard Worker             outIndexToOffset[index] = i;
175*b9df5ad1SAndroid Build Coastguard Worker             outSide[i] = sideFromChannelIdx(index);
176*b9df5ad1SAndroid Build Coastguard Worker             outPair[i] = pairIdxFromChannelIdx(index);
177*b9df5ad1SAndroid Build Coastguard Worker 
178*b9df5ad1SAndroid Build Coastguard Worker             const int channelBit = 1 << index;
179*b9df5ad1SAndroid Build Coastguard Worker             channel &= ~channelBit;
180*b9df5ad1SAndroid Build Coastguard Worker         }
181*b9df5ad1SAndroid Build Coastguard Worker         for (unsigned i = 0; i < outChannels; ++i) {
182*b9df5ad1SAndroid Build Coastguard Worker             if (outPair[i] >= 0 && outPair[i] < (signed)std::size(outIndexToOffset)) {
183*b9df5ad1SAndroid Build Coastguard Worker                 outPair[i] = outIndexToOffset[outPair[i]];
184*b9df5ad1SAndroid Build Coastguard Worker             }
185*b9df5ad1SAndroid Build Coastguard Worker         }
186*b9df5ad1SAndroid Build Coastguard Worker 
187*b9df5ad1SAndroid Build Coastguard Worker         auto remix = IChannelMix::create(outputChannelMask);
188*b9df5ad1SAndroid Build Coastguard Worker 
189*b9df5ad1SAndroid Build Coastguard Worker         for (unsigned i = 0, channel = inputChannelMask; channel != 0; ++i) {
190*b9df5ad1SAndroid Build Coastguard Worker             const int index = __builtin_ctz(channel);
191*b9df5ad1SAndroid Build Coastguard Worker             const int pairIndex = pairIdxFromChannelIdx(index);
192*b9df5ad1SAndroid Build Coastguard Worker             const AUDIO_GEOMETRY_SIDE side = sideFromChannelIdx(index);
193*b9df5ad1SAndroid Build Coastguard Worker             const int channelBit = 1 << index;
194*b9df5ad1SAndroid Build Coastguard Worker             channel &= ~channelBit;
195*b9df5ad1SAndroid Build Coastguard Worker 
196*b9df5ad1SAndroid Build Coastguard Worker             // Generate a +0.5, -0.5 alternating stream in one channel, which has variance 0.25f
197*b9df5ad1SAndroid Build Coastguard Worker             auto indata = input.data();
198*b9df5ad1SAndroid Build Coastguard Worker             for (unsigned j = 0; j < frames; ++j) {
199*b9df5ad1SAndroid Build Coastguard Worker                 for (unsigned k = 0; k < inChannels; ++k) {
200*b9df5ad1SAndroid Build Coastguard Worker                     *indata++ = (k == i) ? (j & 1 ? -0.5f : 0.5f) : 0;
201*b9df5ad1SAndroid Build Coastguard Worker                 }
202*b9df5ad1SAndroid Build Coastguard Worker             }
203*b9df5ad1SAndroid Build Coastguard Worker 
204*b9df5ad1SAndroid Build Coastguard Worker             // Add an offset to the output data - this is ignored if replace instead of accumulate.
205*b9df5ad1SAndroid Build Coastguard Worker             // This must not cause the output to exceed [-1.f, 1.f] otherwise clamping will occur.
206*b9df5ad1SAndroid Build Coastguard Worker             auto outdata = output.data();
207*b9df5ad1SAndroid Build Coastguard Worker             for (unsigned j = 0; j < frames; ++j) {
208*b9df5ad1SAndroid Build Coastguard Worker                 for (unsigned k = 0; k < outChannels; ++k) {
209*b9df5ad1SAndroid Build Coastguard Worker                     *outdata++ = 0.5f;
210*b9df5ad1SAndroid Build Coastguard Worker                 }
211*b9df5ad1SAndroid Build Coastguard Worker             }
212*b9df5ad1SAndroid Build Coastguard Worker 
213*b9df5ad1SAndroid Build Coastguard Worker             // Do the channel mix
214*b9df5ad1SAndroid Build Coastguard Worker             remix->process(input.data(), output.data(), frames, accumulate, inputChannelMask);
215*b9df5ad1SAndroid Build Coastguard Worker 
216*b9df5ad1SAndroid Build Coastguard Worker             // if we accumulate, we need to subtract the initial data offset.
217*b9df5ad1SAndroid Build Coastguard Worker             if (accumulate) {
218*b9df5ad1SAndroid Build Coastguard Worker                 outdata = output.data();
219*b9df5ad1SAndroid Build Coastguard Worker                 for (unsigned j = 0; j < frames; ++j) {
220*b9df5ad1SAndroid Build Coastguard Worker                     for (unsigned k = 0; k < outChannels; ++k) {
221*b9df5ad1SAndroid Build Coastguard Worker                         *outdata++ -= 0.5f;
222*b9df5ad1SAndroid Build Coastguard Worker                     }
223*b9df5ad1SAndroid Build Coastguard Worker                 }
224*b9df5ad1SAndroid Build Coastguard Worker             }
225*b9df5ad1SAndroid Build Coastguard Worker 
226*b9df5ad1SAndroid Build Coastguard Worker             // renormalize the stream to unit amplitude (and unity variance).
227*b9df5ad1SAndroid Build Coastguard Worker             outdata = output.data();
228*b9df5ad1SAndroid Build Coastguard Worker             for (unsigned j = 0; j < frames; ++j) {
229*b9df5ad1SAndroid Build Coastguard Worker                 for (unsigned k = 0; k < outChannels; ++k) {
230*b9df5ad1SAndroid Build Coastguard Worker                     *outdata++ *= 2.f;
231*b9df5ad1SAndroid Build Coastguard Worker                 }
232*b9df5ad1SAndroid Build Coastguard Worker             }
233*b9df5ad1SAndroid Build Coastguard Worker 
234*b9df5ad1SAndroid Build Coastguard Worker             auto stats = channelStatistics(output, outChannels);
235*b9df5ad1SAndroid Build Coastguard Worker             // printf("power: %s %s\n", stats[0].toString().c_str(), stats[1].toString().c_str());
236*b9df5ad1SAndroid Build Coastguard Worker             double power[outChannels];
237*b9df5ad1SAndroid Build Coastguard Worker             for (size_t j = 0; j < outChannels; ++j) {
238*b9df5ad1SAndroid Build Coastguard Worker                 power[j] = stats[j].getPopVariance();
239*b9df5ad1SAndroid Build Coastguard Worker             }
240*b9df5ad1SAndroid Build Coastguard Worker 
241*b9df5ad1SAndroid Build Coastguard Worker             // Check symmetric power for pair channels on exchange of front left/right position.
242*b9df5ad1SAndroid Build Coastguard Worker             // to do this, we save previous power measurements.
243*b9df5ad1SAndroid Build Coastguard Worker             if (pairIndex >= 0 && pairIndex < index) {
244*b9df5ad1SAndroid Build Coastguard Worker 
245*b9df5ad1SAndroid Build Coastguard Worker                 for (unsigned j = 0; j < outChannels; ++j) {
246*b9df5ad1SAndroid Build Coastguard Worker                     if (outPair[j] >= 0) {
247*b9df5ad1SAndroid Build Coastguard Worker                         EXPECT_NEAR_EPSILON(power[j], savedPower[pairIndex][outPair[j]]);
248*b9df5ad1SAndroid Build Coastguard Worker                         EXPECT_NEAR_EPSILON(power[outPair[j]], savedPower[pairIndex][j]);
249*b9df5ad1SAndroid Build Coastguard Worker                     }
250*b9df5ad1SAndroid Build Coastguard Worker                 }
251*b9df5ad1SAndroid Build Coastguard Worker             }
252*b9df5ad1SAndroid Build Coastguard Worker             for (unsigned j = 0; j < outChannels; ++j) {
253*b9df5ad1SAndroid Build Coastguard Worker                 savedPower[index][j] = power[j];
254*b9df5ad1SAndroid Build Coastguard Worker             }
255*b9df5ad1SAndroid Build Coastguard Worker 
256*b9df5ad1SAndroid Build Coastguard Worker             // For downmix to stereo, we compare exact values to a predefined matrix.
257*b9df5ad1SAndroid Build Coastguard Worker             const bool checkExpectedPower = outputChannelMask == AUDIO_CHANNEL_OUT_STEREO;
258*b9df5ad1SAndroid Build Coastguard Worker             constexpr size_t FL = 0;
259*b9df5ad1SAndroid Build Coastguard Worker             constexpr size_t FR = 1;
260*b9df5ad1SAndroid Build Coastguard Worker 
261*b9df5ad1SAndroid Build Coastguard Worker             // Confirm exactly the mix amount prescribed by the existing ChannelMix effect.
262*b9df5ad1SAndroid Build Coastguard Worker             // For future changes to the ChannelMix effect, the nearness needs to be relaxed
263*b9df5ad1SAndroid Build Coastguard Worker             // to compare behavior S or earlier.
264*b9df5ad1SAndroid Build Coastguard Worker 
265*b9df5ad1SAndroid Build Coastguard Worker             constexpr float POWER_TOLERANCE = 0.001;
266*b9df5ad1SAndroid Build Coastguard Worker             const float expectedPower = checkExpectedPower ?
267*b9df5ad1SAndroid Build Coastguard Worker                     kScaleFromChannelIdxLeft[index] * kScaleFromChannelIdxLeft[index]
268*b9df5ad1SAndroid Build Coastguard Worker                     + kScaleFromChannelIdxRight[index] * kScaleFromChannelIdxRight[index] : 0;
269*b9df5ad1SAndroid Build Coastguard Worker 
270*b9df5ad1SAndroid Build Coastguard Worker             if (checkExpectedPower) {
271*b9df5ad1SAndroid Build Coastguard Worker                 EXPECT_NEAR(expectedPower, power[FL] + power[FR], POWER_TOLERANCE);
272*b9df5ad1SAndroid Build Coastguard Worker             }
273*b9df5ad1SAndroid Build Coastguard Worker             switch (side) {
274*b9df5ad1SAndroid Build Coastguard Worker             case AUDIO_GEOMETRY_SIDE_LEFT:
275*b9df5ad1SAndroid Build Coastguard Worker                 if (channelBit == AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER) {
276*b9df5ad1SAndroid Build Coastguard Worker                     break;
277*b9df5ad1SAndroid Build Coastguard Worker                 }
278*b9df5ad1SAndroid Build Coastguard Worker                 for (unsigned j = 0; j < outChannels; ++j) {
279*b9df5ad1SAndroid Build Coastguard Worker                     if (outSide[j] == AUDIO_GEOMETRY_SIDE_RIGHT) {
280*b9df5ad1SAndroid Build Coastguard Worker                         EXPECT_EQ(0.f, power[j]);
281*b9df5ad1SAndroid Build Coastguard Worker                     }
282*b9df5ad1SAndroid Build Coastguard Worker                 }
283*b9df5ad1SAndroid Build Coastguard Worker                 break;
284*b9df5ad1SAndroid Build Coastguard Worker             case AUDIO_GEOMETRY_SIDE_RIGHT:
285*b9df5ad1SAndroid Build Coastguard Worker                 if (channelBit == AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER) {
286*b9df5ad1SAndroid Build Coastguard Worker                     break;
287*b9df5ad1SAndroid Build Coastguard Worker                 }
288*b9df5ad1SAndroid Build Coastguard Worker                 for (unsigned j = 0; j < outChannels; ++j) {
289*b9df5ad1SAndroid Build Coastguard Worker                     if (outSide[j] == AUDIO_GEOMETRY_SIDE_LEFT) {
290*b9df5ad1SAndroid Build Coastguard Worker                         EXPECT_EQ(0.f, power[j]);
291*b9df5ad1SAndroid Build Coastguard Worker                     }
292*b9df5ad1SAndroid Build Coastguard Worker                 }
293*b9df5ad1SAndroid Build Coastguard Worker                 break;
294*b9df5ad1SAndroid Build Coastguard Worker             case AUDIO_GEOMETRY_SIDE_CENTER:
295*b9df5ad1SAndroid Build Coastguard Worker                 if (channelBit == AUDIO_CHANNEL_OUT_LOW_FREQUENCY) {
296*b9df5ad1SAndroid Build Coastguard Worker                     if (inputChannelMask & AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2) {
297*b9df5ad1SAndroid Build Coastguard Worker                         EXPECT_EQ(0.f, power[FR]);
298*b9df5ad1SAndroid Build Coastguard Worker                         break;
299*b9df5ad1SAndroid Build Coastguard Worker                     } else {
300*b9df5ad1SAndroid Build Coastguard Worker                         for (unsigned j = 0; j < outChannels; ++j) {
301*b9df5ad1SAndroid Build Coastguard Worker                             if (outPair[j] >= 0) {
302*b9df5ad1SAndroid Build Coastguard Worker                                 EXPECT_NEAR_EPSILON(power[j], power[outPair[j]]);
303*b9df5ad1SAndroid Build Coastguard Worker                             }
304*b9df5ad1SAndroid Build Coastguard Worker                         }
305*b9df5ad1SAndroid Build Coastguard Worker                         if (checkExpectedPower) {
306*b9df5ad1SAndroid Build Coastguard Worker                             EXPECT_NEAR(expectedPower, power[FL] + power[FR], POWER_TOLERANCE);
307*b9df5ad1SAndroid Build Coastguard Worker                         }
308*b9df5ad1SAndroid Build Coastguard Worker                         break;
309*b9df5ad1SAndroid Build Coastguard Worker                     }
310*b9df5ad1SAndroid Build Coastguard Worker                 } else if (channelBit == AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2) {
311*b9df5ad1SAndroid Build Coastguard Worker                     EXPECT_EQ(0.f, power[FL]);
312*b9df5ad1SAndroid Build Coastguard Worker                     if (checkExpectedPower) {
313*b9df5ad1SAndroid Build Coastguard Worker                         EXPECT_NEAR(expectedPower, power[FR], POWER_TOLERANCE);
314*b9df5ad1SAndroid Build Coastguard Worker                     }
315*b9df5ad1SAndroid Build Coastguard Worker                     break;
316*b9df5ad1SAndroid Build Coastguard Worker                 }
317*b9df5ad1SAndroid Build Coastguard Worker                 for (unsigned j = 0; j < outChannels; ++j) {
318*b9df5ad1SAndroid Build Coastguard Worker                     if (outPair[j] >= 0) {
319*b9df5ad1SAndroid Build Coastguard Worker                         EXPECT_NEAR_EPSILON(power[j], power[outPair[j]]);
320*b9df5ad1SAndroid Build Coastguard Worker                     }
321*b9df5ad1SAndroid Build Coastguard Worker                 }
322*b9df5ad1SAndroid Build Coastguard Worker                 break;
323*b9df5ad1SAndroid Build Coastguard Worker             }
324*b9df5ad1SAndroid Build Coastguard Worker         }
325*b9df5ad1SAndroid Build Coastguard Worker     }
326*b9df5ad1SAndroid Build Coastguard Worker };
327*b9df5ad1SAndroid Build Coastguard Worker 
328*b9df5ad1SAndroid Build Coastguard Worker static constexpr const char *kName1[] = {"_replace_", "_accumulate_"};
329*b9df5ad1SAndroid Build Coastguard Worker 
330*b9df5ad1SAndroid Build Coastguard Worker // The Balance test checks that the power output is symmetric with left / right channel swap.
331*b9df5ad1SAndroid Build Coastguard Worker 
TEST_P(ChannelMixTest,balance)332*b9df5ad1SAndroid Build Coastguard Worker TEST_P(ChannelMixTest, balance) {
333*b9df5ad1SAndroid Build Coastguard Worker     testBalance(kOutputChannelMasks[std::get<OUTPUT_CHANNEL_MASK_POSITION>(GetParam())],
334*b9df5ad1SAndroid Build Coastguard Worker             kInputChannelMasks[std::get<INPUT_CHANNEL_MASK_POSITION>(GetParam())],
335*b9df5ad1SAndroid Build Coastguard Worker             std::get<ACCUMULATE_POSITION>(GetParam()));
336*b9df5ad1SAndroid Build Coastguard Worker }
337*b9df5ad1SAndroid Build Coastguard Worker 
338*b9df5ad1SAndroid Build Coastguard Worker INSTANTIATE_TEST_SUITE_P(
339*b9df5ad1SAndroid Build Coastguard Worker         ChannelMixTestAll, ChannelMixTest,
340*b9df5ad1SAndroid Build Coastguard Worker         ::testing::Combine(
341*b9df5ad1SAndroid Build Coastguard Worker                 ::testing::Range(0, (int)std::size(kOutputChannelMasks)),
342*b9df5ad1SAndroid Build Coastguard Worker                 ::testing::Range(0, (int)std::size(kInputChannelMasks)),
343*b9df5ad1SAndroid Build Coastguard Worker                 ::testing::Bool() // accumulate off, on
344*b9df5ad1SAndroid Build Coastguard Worker                 ),
__anonf37717750102(const testing::TestParamInfo<ChannelMixTest::ParamType>& info) 345*b9df5ad1SAndroid Build Coastguard Worker         [](const testing::TestParamInfo<ChannelMixTest::ParamType>& info) {
346*b9df5ad1SAndroid Build Coastguard Worker             const int out_index = std::get<OUTPUT_CHANNEL_MASK_POSITION>(info.param);
347*b9df5ad1SAndroid Build Coastguard Worker             const audio_channel_mask_t outputChannelMask = kOutputChannelMasks[out_index];
348*b9df5ad1SAndroid Build Coastguard Worker             const int in_index = std::get<INPUT_CHANNEL_MASK_POSITION>(info.param);
349*b9df5ad1SAndroid Build Coastguard Worker             const audio_channel_mask_t inputChannelMask = kInputChannelMasks[in_index];
350*b9df5ad1SAndroid Build Coastguard Worker             const std::string name =
351*b9df5ad1SAndroid Build Coastguard Worker                     std::string(audio_channel_out_mask_to_string(outputChannelMask)) +
352*b9df5ad1SAndroid Build Coastguard Worker                     "_" + std::string(audio_channel_out_mask_to_string(inputChannelMask)) +
353*b9df5ad1SAndroid Build Coastguard Worker                     kName1[std::get<ACCUMULATE_POSITION>(info.param)] + std::to_string(in_index);
354*b9df5ad1SAndroid Build Coastguard Worker             return name;
355*b9df5ad1SAndroid Build Coastguard Worker         });
356*b9df5ad1SAndroid Build Coastguard Worker 
357*b9df5ad1SAndroid Build Coastguard Worker // --------------------------------------------------------------------------------------
358*b9df5ad1SAndroid Build Coastguard Worker 
359*b9df5ad1SAndroid Build Coastguard Worker using ChannelMixIdentityParam = std::tuple<int /* channel mask */, bool /* accumulate */>;
360*b9df5ad1SAndroid Build Coastguard Worker 
361*b9df5ad1SAndroid Build Coastguard Worker enum {
362*b9df5ad1SAndroid Build Coastguard Worker     IDENTITY_CHANNEL_MASK_POSITION = 0,
363*b9df5ad1SAndroid Build Coastguard Worker     IDENTITY_ACCUMULATE_POSITION = 1,
364*b9df5ad1SAndroid Build Coastguard Worker };
365*b9df5ad1SAndroid Build Coastguard Worker 
366*b9df5ad1SAndroid Build Coastguard Worker class ChannelMixIdentityTest : public ::testing::TestWithParam<ChannelMixIdentityParam> {
367*b9df5ad1SAndroid Build Coastguard Worker public:
368*b9df5ad1SAndroid Build Coastguard Worker 
testIdentity(audio_channel_mask_t channelMask,bool accumulate)369*b9df5ad1SAndroid Build Coastguard Worker     void testIdentity(audio_channel_mask_t channelMask, bool accumulate) {
370*b9df5ad1SAndroid Build Coastguard Worker         const size_t frames = 100;
371*b9df5ad1SAndroid Build Coastguard Worker         const unsigned channels = audio_channel_count_from_out_mask(channelMask);
372*b9df5ad1SAndroid Build Coastguard Worker         std::vector<float> input(frames * channels);
373*b9df5ad1SAndroid Build Coastguard Worker         std::vector<float> output(frames * channels);
374*b9df5ad1SAndroid Build Coastguard Worker 
375*b9df5ad1SAndroid Build Coastguard Worker         auto remix = ::android::audio_utils::channels::IChannelMix::create(channelMask);
376*b9df5ad1SAndroid Build Coastguard Worker 
377*b9df5ad1SAndroid Build Coastguard Worker         constexpr float kInvalid = -0.7f;
378*b9df5ad1SAndroid Build Coastguard Worker         constexpr float kImpulse = 0.3f;
379*b9df5ad1SAndroid Build Coastguard Worker 
380*b9df5ad1SAndroid Build Coastguard Worker         for (size_t i = 0; i < channels; ++i) {
381*b9df5ad1SAndroid Build Coastguard Worker             // A remix with one of the channels specified should equal itself.
382*b9df5ad1SAndroid Build Coastguard Worker 
383*b9df5ad1SAndroid Build Coastguard Worker             std::fill(input.begin(), input.end(), 0.f);
384*b9df5ad1SAndroid Build Coastguard Worker             if (!accumulate) std::fill(output.begin(), output.end(), kInvalid);
385*b9df5ad1SAndroid Build Coastguard Worker             for (size_t j = 0; j < frames; ++j) {
386*b9df5ad1SAndroid Build Coastguard Worker                 input[j * channels + i] = kImpulse;
387*b9df5ad1SAndroid Build Coastguard Worker             }
388*b9df5ad1SAndroid Build Coastguard Worker 
389*b9df5ad1SAndroid Build Coastguard Worker             // Do the channel mix
390*b9df5ad1SAndroid Build Coastguard Worker             remix->process(input.data(), output.data(), frames, false /* accumulate */,
391*b9df5ad1SAndroid Build Coastguard Worker                            channelMask);
392*b9df5ad1SAndroid Build Coastguard Worker 
393*b9df5ad1SAndroid Build Coastguard Worker             EXPECT_EQ(0, memcmp(input.data(), output.data(), frames * channels * sizeof(float)));
394*b9df5ad1SAndroid Build Coastguard Worker         }
395*b9df5ad1SAndroid Build Coastguard Worker     }
396*b9df5ad1SAndroid Build Coastguard Worker };
397*b9df5ad1SAndroid Build Coastguard Worker 
398*b9df5ad1SAndroid Build Coastguard Worker // The Identity test checks that putting audio data on an input channel included in the
399*b9df5ad1SAndroid Build Coastguard Worker // destination channel mask must be preserved on the same channel on the output.
400*b9df5ad1SAndroid Build Coastguard Worker 
401*b9df5ad1SAndroid Build Coastguard Worker // For simplicity, we use the same channel mask for input and output.
402*b9df5ad1SAndroid Build Coastguard Worker // This is not optimized out here because it doesn't happen in practice: we only set
403*b9df5ad1SAndroid Build Coastguard Worker // up the ChannelMix object when the channel mask differs.
404*b9df5ad1SAndroid Build Coastguard Worker 
TEST_P(ChannelMixIdentityTest,identity)405*b9df5ad1SAndroid Build Coastguard Worker TEST_P(ChannelMixIdentityTest, identity) {
406*b9df5ad1SAndroid Build Coastguard Worker     testIdentity(kOutputChannelMasks[std::get<IDENTITY_CHANNEL_MASK_POSITION>(GetParam())],
407*b9df5ad1SAndroid Build Coastguard Worker             std::get<IDENTITY_ACCUMULATE_POSITION>(GetParam()));
408*b9df5ad1SAndroid Build Coastguard Worker }
409*b9df5ad1SAndroid Build Coastguard Worker 
410*b9df5ad1SAndroid Build Coastguard Worker INSTANTIATE_TEST_SUITE_P(
411*b9df5ad1SAndroid Build Coastguard Worker         ChannelMixIdentityTestAll, ChannelMixIdentityTest,
412*b9df5ad1SAndroid Build Coastguard Worker         ::testing::Combine(
413*b9df5ad1SAndroid Build Coastguard Worker             ::testing::Range(0, (int)std::size(kOutputChannelMasks)),
414*b9df5ad1SAndroid Build Coastguard Worker             ::testing::Bool() // accumulate off, on
415*b9df5ad1SAndroid Build Coastguard Worker         ),
__anonf37717750302(const testing::TestParamInfo<ChannelMixIdentityTest::ParamType>& info) 416*b9df5ad1SAndroid Build Coastguard Worker         [](const testing::TestParamInfo<ChannelMixIdentityTest::ParamType>& info) {
417*b9df5ad1SAndroid Build Coastguard Worker             const int index = std::get<IDENTITY_CHANNEL_MASK_POSITION>(info.param);
418*b9df5ad1SAndroid Build Coastguard Worker             const audio_channel_mask_t channelMask = kOutputChannelMasks[index];
419*b9df5ad1SAndroid Build Coastguard Worker             const std::string name =
420*b9df5ad1SAndroid Build Coastguard Worker                     std::string(audio_channel_out_mask_to_string(channelMask)) +
421*b9df5ad1SAndroid Build Coastguard Worker                     kName1[std::get<IDENTITY_ACCUMULATE_POSITION>(info.param)] +
422*b9df5ad1SAndroid Build Coastguard Worker                     std::to_string(index);
423*b9df5ad1SAndroid Build Coastguard Worker             return name;
424*b9df5ad1SAndroid Build Coastguard Worker         });
425*b9df5ad1SAndroid Build Coastguard Worker 
426*b9df5ad1SAndroid Build Coastguard Worker // --------------------------------------------------------------------------------------
427*b9df5ad1SAndroid Build Coastguard Worker 
428*b9df5ad1SAndroid Build Coastguard Worker using StereoDownMix = android::audio_utils::channels::ChannelMix<AUDIO_CHANNEL_OUT_STEREO>;
TEST(channelmix,input_channel_mask)429*b9df5ad1SAndroid Build Coastguard Worker TEST(channelmix, input_channel_mask) {
430*b9df5ad1SAndroid Build Coastguard Worker     using namespace ::android::audio_utils::channels;
431*b9df5ad1SAndroid Build Coastguard Worker     StereoDownMix channelMix(AUDIO_CHANNEL_NONE);
432*b9df5ad1SAndroid Build Coastguard Worker 
433*b9df5ad1SAndroid Build Coastguard Worker     ASSERT_EQ(AUDIO_CHANNEL_NONE, channelMix.getInputChannelMask());
434*b9df5ad1SAndroid Build Coastguard Worker     ASSERT_TRUE(channelMix.setInputChannelMask(AUDIO_CHANNEL_OUT_STEREO));
435*b9df5ad1SAndroid Build Coastguard Worker     ASSERT_EQ(AUDIO_CHANNEL_OUT_STEREO, channelMix.getInputChannelMask());
436*b9df5ad1SAndroid Build Coastguard Worker }
437