xref: /aosp_15_r20/system/media/audio_utils/Balance.cpp (revision b9df5ad1c9ac98a7fefaac271a55f7ae3db05414)
1*b9df5ad1SAndroid Build Coastguard Worker /*
2*b9df5ad1SAndroid Build Coastguard Worker  * Copyright (C) 2019 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/Balance.h>
18*b9df5ad1SAndroid Build Coastguard Worker 
19*b9df5ad1SAndroid Build Coastguard Worker namespace android::audio_utils {
20*b9df5ad1SAndroid Build Coastguard Worker 
setChannelMask(audio_channel_mask_t channelMask)21*b9df5ad1SAndroid Build Coastguard Worker void Balance::setChannelMask(audio_channel_mask_t channelMask)
22*b9df5ad1SAndroid Build Coastguard Worker {
23*b9df5ad1SAndroid Build Coastguard Worker     using namespace ::android::audio_utils::channels;
24*b9df5ad1SAndroid Build Coastguard Worker     channelMask = static_cast<audio_channel_mask_t>(channelMask & ~AUDIO_CHANNEL_HAPTIC_ALL);
25*b9df5ad1SAndroid Build Coastguard Worker     if (!audio_is_output_channel(channelMask) // invalid mask
26*b9df5ad1SAndroid Build Coastguard Worker             || mChannelMask == channelMask) { // no need to do anything
27*b9df5ad1SAndroid Build Coastguard Worker         return;
28*b9df5ad1SAndroid Build Coastguard Worker     }
29*b9df5ad1SAndroid Build Coastguard Worker 
30*b9df5ad1SAndroid Build Coastguard Worker     mChannelMask = channelMask;
31*b9df5ad1SAndroid Build Coastguard Worker     mChannelCount = audio_channel_count_from_out_mask(channelMask);
32*b9df5ad1SAndroid Build Coastguard Worker 
33*b9df5ad1SAndroid Build Coastguard Worker     // save mBalance into balance for later restoring, then reset
34*b9df5ad1SAndroid Build Coastguard Worker     const float balance = mBalance;
35*b9df5ad1SAndroid Build Coastguard Worker     mBalance = 0.f;
36*b9df5ad1SAndroid Build Coastguard Worker 
37*b9df5ad1SAndroid Build Coastguard Worker     // reset mVolumes
38*b9df5ad1SAndroid Build Coastguard Worker     mVolumes.resize(mChannelCount);
39*b9df5ad1SAndroid Build Coastguard Worker     std::fill(mVolumes.begin(), mVolumes.end(), 1.f);
40*b9df5ad1SAndroid Build Coastguard Worker 
41*b9df5ad1SAndroid Build Coastguard Worker     // reset ramping variables
42*b9df5ad1SAndroid Build Coastguard Worker     mRampBalance = 0.f;
43*b9df5ad1SAndroid Build Coastguard Worker     mRampVolumes.clear();
44*b9df5ad1SAndroid Build Coastguard Worker 
45*b9df5ad1SAndroid Build Coastguard Worker     if (audio_channel_mask_get_representation(mChannelMask)
46*b9df5ad1SAndroid Build Coastguard Worker             == AUDIO_CHANNEL_REPRESENTATION_INDEX) {
47*b9df5ad1SAndroid Build Coastguard Worker         mSides.clear();       // mSides unused for channel index masks.
48*b9df5ad1SAndroid Build Coastguard Worker         setBalance(balance);  // recompute balance
49*b9df5ad1SAndroid Build Coastguard Worker         return;
50*b9df5ad1SAndroid Build Coastguard Worker     }
51*b9df5ad1SAndroid Build Coastguard Worker 
52*b9df5ad1SAndroid Build Coastguard Worker     mSides.resize(mChannelCount);
53*b9df5ad1SAndroid Build Coastguard Worker     // If LFE and LFE2 both exist, it should be L and R in 22.2
54*b9df5ad1SAndroid Build Coastguard Worker     int lfe = -1;
55*b9df5ad1SAndroid Build Coastguard Worker     int lfe2 = -1;
56*b9df5ad1SAndroid Build Coastguard Worker     constexpr unsigned LFE_CHANNEL_INDEX = 3;
57*b9df5ad1SAndroid Build Coastguard Worker     constexpr unsigned LFE2_CHANNEL_INDEX = 23;
58*b9df5ad1SAndroid Build Coastguard Worker     for (unsigned i = 0, channel = channelMask; channel != 0; ++i) {
59*b9df5ad1SAndroid Build Coastguard Worker         const int index = __builtin_ctz(channel);
60*b9df5ad1SAndroid Build Coastguard Worker         mSides[i] = sideFromChannelIdx(index);
61*b9df5ad1SAndroid Build Coastguard Worker         // Keep track of LFE indices
62*b9df5ad1SAndroid Build Coastguard Worker         if (index == LFE_CHANNEL_INDEX) {
63*b9df5ad1SAndroid Build Coastguard Worker             lfe = i;
64*b9df5ad1SAndroid Build Coastguard Worker         } else if (index == LFE2_CHANNEL_INDEX) {
65*b9df5ad1SAndroid Build Coastguard Worker             lfe2 = i;
66*b9df5ad1SAndroid Build Coastguard Worker         }
67*b9df5ad1SAndroid Build Coastguard Worker         channel &= ~(1 << index);
68*b9df5ad1SAndroid Build Coastguard Worker     }
69*b9df5ad1SAndroid Build Coastguard Worker     if (lfe >= 0 && lfe2 >= 0) { // if both LFEs exist assign to L and R.
70*b9df5ad1SAndroid Build Coastguard Worker         mSides[lfe] = AUDIO_GEOMETRY_SIDE_LEFT;
71*b9df5ad1SAndroid Build Coastguard Worker         mSides[lfe2] = AUDIO_GEOMETRY_SIDE_RIGHT;
72*b9df5ad1SAndroid Build Coastguard Worker     }
73*b9df5ad1SAndroid Build Coastguard Worker     setBalance(balance); // recompute balance
74*b9df5ad1SAndroid Build Coastguard Worker }
75*b9df5ad1SAndroid Build Coastguard Worker 
process(float * buffer,size_t frames)76*b9df5ad1SAndroid Build Coastguard Worker void Balance::process(float *buffer, size_t frames)
77*b9df5ad1SAndroid Build Coastguard Worker {
78*b9df5ad1SAndroid Build Coastguard Worker     if (mBalance == 0.f || mChannelCount < 2) {
79*b9df5ad1SAndroid Build Coastguard Worker         return;
80*b9df5ad1SAndroid Build Coastguard Worker     }
81*b9df5ad1SAndroid Build Coastguard Worker 
82*b9df5ad1SAndroid Build Coastguard Worker     if (mRamp) {
83*b9df5ad1SAndroid Build Coastguard Worker         if (mRampVolumes.size() != mVolumes.size()) {
84*b9df5ad1SAndroid Build Coastguard Worker             // If mRampVolumes is empty, we do not ramp in this process() but directly
85*b9df5ad1SAndroid Build Coastguard Worker             // apply the existing mVolumes. We save the balance and volume state here
86*b9df5ad1SAndroid Build Coastguard Worker             // and fall through to non-ramping code below. The next process() will ramp if needed.
87*b9df5ad1SAndroid Build Coastguard Worker             mRampBalance = mBalance;
88*b9df5ad1SAndroid Build Coastguard Worker             mRampVolumes = mVolumes;
89*b9df5ad1SAndroid Build Coastguard Worker         } else if (mRampBalance != mBalance) {
90*b9df5ad1SAndroid Build Coastguard Worker             if (frames > 0) {
91*b9df5ad1SAndroid Build Coastguard Worker                 std::vector<float> mDeltas(mVolumes.size());
92*b9df5ad1SAndroid Build Coastguard Worker                 const float r = 1.f / frames;
93*b9df5ad1SAndroid Build Coastguard Worker                 for (size_t j = 0; j < mChannelCount; ++j) {
94*b9df5ad1SAndroid Build Coastguard Worker                     mDeltas[j] = (mVolumes[j] - mRampVolumes[j]) * r;
95*b9df5ad1SAndroid Build Coastguard Worker                 }
96*b9df5ad1SAndroid Build Coastguard Worker 
97*b9df5ad1SAndroid Build Coastguard Worker                 // ramped balance
98*b9df5ad1SAndroid Build Coastguard Worker                 for (size_t i = 0; i < frames; ++i) {
99*b9df5ad1SAndroid Build Coastguard Worker                     const float findex = i;
100*b9df5ad1SAndroid Build Coastguard Worker                     for (size_t j = 0; j < mChannelCount; ++j) { // better precision: delta * i
101*b9df5ad1SAndroid Build Coastguard Worker                         *buffer++ *= mRampVolumes[j] + mDeltas[j] * findex;
102*b9df5ad1SAndroid Build Coastguard Worker                     }
103*b9df5ad1SAndroid Build Coastguard Worker                 }
104*b9df5ad1SAndroid Build Coastguard Worker             }
105*b9df5ad1SAndroid Build Coastguard Worker             mRampBalance = mBalance;
106*b9df5ad1SAndroid Build Coastguard Worker             mRampVolumes = mVolumes;
107*b9df5ad1SAndroid Build Coastguard Worker             return;
108*b9df5ad1SAndroid Build Coastguard Worker         }
109*b9df5ad1SAndroid Build Coastguard Worker         // fall through
110*b9df5ad1SAndroid Build Coastguard Worker     }
111*b9df5ad1SAndroid Build Coastguard Worker 
112*b9df5ad1SAndroid Build Coastguard Worker     // non-ramped balance
113*b9df5ad1SAndroid Build Coastguard Worker     for (size_t i = 0; i < frames; ++i) {
114*b9df5ad1SAndroid Build Coastguard Worker         for (size_t j = 0; j < mChannelCount; ++j) {
115*b9df5ad1SAndroid Build Coastguard Worker             *buffer++ *= mVolumes[j];
116*b9df5ad1SAndroid Build Coastguard Worker         }
117*b9df5ad1SAndroid Build Coastguard Worker     }
118*b9df5ad1SAndroid Build Coastguard Worker }
119*b9df5ad1SAndroid Build Coastguard Worker 
computeStereoBalance(float balance,float * left,float * right) const120*b9df5ad1SAndroid Build Coastguard Worker void Balance::computeStereoBalance(float balance, float *left, float *right) const
121*b9df5ad1SAndroid Build Coastguard Worker {
122*b9df5ad1SAndroid Build Coastguard Worker     if (balance > 0.f) {
123*b9df5ad1SAndroid Build Coastguard Worker         *left = mCurve(1.f - balance);
124*b9df5ad1SAndroid Build Coastguard Worker         *right = 1.f;
125*b9df5ad1SAndroid Build Coastguard Worker     } else if (balance < 0.f) {
126*b9df5ad1SAndroid Build Coastguard Worker         *left = 1.f;
127*b9df5ad1SAndroid Build Coastguard Worker         *right = mCurve(1.f + balance);
128*b9df5ad1SAndroid Build Coastguard Worker     } else {
129*b9df5ad1SAndroid Build Coastguard Worker         *left = 1.f;
130*b9df5ad1SAndroid Build Coastguard Worker         *right = 1.f;
131*b9df5ad1SAndroid Build Coastguard Worker     }
132*b9df5ad1SAndroid Build Coastguard Worker 
133*b9df5ad1SAndroid Build Coastguard Worker     // Functionally:
134*b9df5ad1SAndroid Build Coastguard Worker     // *left = balance > 0.f ? mCurve(1.f - balance) : 1.f;
135*b9df5ad1SAndroid Build Coastguard Worker     // *right = balance < 0.f ? mCurve(1.f + balance) : 1.f;
136*b9df5ad1SAndroid Build Coastguard Worker }
137*b9df5ad1SAndroid Build Coastguard Worker 
toString() const138*b9df5ad1SAndroid Build Coastguard Worker std::string Balance::toString() const
139*b9df5ad1SAndroid Build Coastguard Worker {
140*b9df5ad1SAndroid Build Coastguard Worker     std::stringstream ss;
141*b9df5ad1SAndroid Build Coastguard Worker     ss << "balance " << mBalance << " channelCount " << mChannelCount << " volumes:";
142*b9df5ad1SAndroid Build Coastguard Worker     for (float volume : mVolumes) {
143*b9df5ad1SAndroid Build Coastguard Worker         ss << " " << volume;
144*b9df5ad1SAndroid Build Coastguard Worker     }
145*b9df5ad1SAndroid Build Coastguard Worker     // we do not show mSides, which is only valid for channel position masks.
146*b9df5ad1SAndroid Build Coastguard Worker     return ss.str();
147*b9df5ad1SAndroid Build Coastguard Worker }
148*b9df5ad1SAndroid Build Coastguard Worker 
setBalance(float balance)149*b9df5ad1SAndroid Build Coastguard Worker void Balance::setBalance(float balance)
150*b9df5ad1SAndroid Build Coastguard Worker {
151*b9df5ad1SAndroid Build Coastguard Worker     using namespace ::android::audio_utils::channels;
152*b9df5ad1SAndroid Build Coastguard Worker     if (mBalance == balance                         // no change
153*b9df5ad1SAndroid Build Coastguard Worker         || isnan(balance) || fabs(balance) > 1.f) { // balance out of range
154*b9df5ad1SAndroid Build Coastguard Worker         return;
155*b9df5ad1SAndroid Build Coastguard Worker     }
156*b9df5ad1SAndroid Build Coastguard Worker 
157*b9df5ad1SAndroid Build Coastguard Worker     mBalance = balance;
158*b9df5ad1SAndroid Build Coastguard Worker 
159*b9df5ad1SAndroid Build Coastguard Worker     if (mChannelCount < 2) { // if channel count is 1, mVolumes[0] is already set to 1.f
160*b9df5ad1SAndroid Build Coastguard Worker         return;              // and if channel count < 2, we don't do anything in process().
161*b9df5ad1SAndroid Build Coastguard Worker     }
162*b9df5ad1SAndroid Build Coastguard Worker 
163*b9df5ad1SAndroid Build Coastguard Worker     // Handle the common cases:
164*b9df5ad1SAndroid Build Coastguard Worker     // stereo and channel index masks only affect the first two channels as left and right.
165*b9df5ad1SAndroid Build Coastguard Worker     if (mChannelMask == AUDIO_CHANNEL_OUT_STEREO
166*b9df5ad1SAndroid Build Coastguard Worker             || audio_channel_mask_get_representation(mChannelMask)
167*b9df5ad1SAndroid Build Coastguard Worker                     == AUDIO_CHANNEL_REPRESENTATION_INDEX) {
168*b9df5ad1SAndroid Build Coastguard Worker         computeStereoBalance(balance, &mVolumes[0], &mVolumes[1]);
169*b9df5ad1SAndroid Build Coastguard Worker         return;
170*b9df5ad1SAndroid Build Coastguard Worker     }
171*b9df5ad1SAndroid Build Coastguard Worker 
172*b9df5ad1SAndroid Build Coastguard Worker     // For position masks with more than 2 channels, we consider which side the
173*b9df5ad1SAndroid Build Coastguard Worker     // speaker position is on to figure the volume used.
174*b9df5ad1SAndroid Build Coastguard Worker     float balanceVolumes[3]; // left, right, center (we don't care the order)
175*b9df5ad1SAndroid Build Coastguard Worker     static_assert(AUDIO_GEOMETRY_SIDE_LEFT >= 0
176*b9df5ad1SAndroid Build Coastguard Worker             && AUDIO_GEOMETRY_SIDE_LEFT <= std::size(balanceVolumes));
177*b9df5ad1SAndroid Build Coastguard Worker     static_assert(AUDIO_GEOMETRY_SIDE_RIGHT >= 0
178*b9df5ad1SAndroid Build Coastguard Worker             && AUDIO_GEOMETRY_SIDE_RIGHT <= std::size(balanceVolumes));
179*b9df5ad1SAndroid Build Coastguard Worker     static_assert(AUDIO_GEOMETRY_SIDE_CENTER >= 0
180*b9df5ad1SAndroid Build Coastguard Worker             && AUDIO_GEOMETRY_SIDE_CENTER <= std::size(balanceVolumes));
181*b9df5ad1SAndroid Build Coastguard Worker     computeStereoBalance(balance, &balanceVolumes[AUDIO_GEOMETRY_SIDE_LEFT],
182*b9df5ad1SAndroid Build Coastguard Worker             &balanceVolumes[AUDIO_GEOMETRY_SIDE_RIGHT]);
183*b9df5ad1SAndroid Build Coastguard Worker     balanceVolumes[AUDIO_GEOMETRY_SIDE_CENTER] = 1.f; // center  TODO: consider center scaling.
184*b9df5ad1SAndroid Build Coastguard Worker 
185*b9df5ad1SAndroid Build Coastguard Worker     for (size_t i = 0; i < mVolumes.size(); ++i) {
186*b9df5ad1SAndroid Build Coastguard Worker         mVolumes[i] = balanceVolumes[mSides[i]];
187*b9df5ad1SAndroid Build Coastguard Worker     }
188*b9df5ad1SAndroid Build Coastguard Worker }
189*b9df5ad1SAndroid Build Coastguard Worker 
190*b9df5ad1SAndroid Build Coastguard Worker } // namespace android::audio_utils
191