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