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 channelMask &= ~ AUDIO_CHANNEL_HAPTIC_ALL;
24 if (!audio_is_output_channel(channelMask) // invalid mask
25 || mChannelMask == channelMask) { // no need to do anything
26 return;
27 }
28
29 mChannelMask = channelMask;
30 mChannelCount = audio_channel_count_from_out_mask(channelMask);
31
32 // save mBalance into balance for later restoring, then reset
33 const float balance = mBalance;
34 mBalance = 0.f;
35
36 // reset mVolumes
37 mVolumes.resize(mChannelCount);
38 std::fill(mVolumes.begin(), mVolumes.end(), 1.f);
39
40 // reset ramping variables
41 mRampBalance = 0.f;
42 mRampVolumes.clear();
43
44 if (audio_channel_mask_get_representation(mChannelMask)
45 == AUDIO_CHANNEL_REPRESENTATION_INDEX) {
46 mSides.clear(); // mSides unused for channel index masks.
47 setBalance(balance); // recompute balance
48 return;
49 }
50
51 // Implementation detail (may change):
52 // For implementation speed, we precompute the side (left, right, center),
53 // which is a fixed geometrical constant for a given channel mask.
54 // This assumes that the channel mask does not change frequently.
55 //
56 // For the channel mask spec, see system/media/audio/include/system/audio-base.h.
57 //
58 // The side is: 0 = left, 1 = right, 2 = center.
59 static constexpr int sideFromChannel[] = {
60 0, // AUDIO_CHANNEL_OUT_FRONT_LEFT = 0x1u,
61 1, // AUDIO_CHANNEL_OUT_FRONT_RIGHT = 0x2u,
62 2, // AUDIO_CHANNEL_OUT_FRONT_CENTER = 0x4u,
63 2, // AUDIO_CHANNEL_OUT_LOW_FREQUENCY = 0x8u,
64 0, // AUDIO_CHANNEL_OUT_BACK_LEFT = 0x10u,
65 1, // AUDIO_CHANNEL_OUT_BACK_RIGHT = 0x20u,
66 0, // AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x40u,
67 1, // AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x80u,
68 2, // AUDIO_CHANNEL_OUT_BACK_CENTER = 0x100u,
69 0, // AUDIO_CHANNEL_OUT_SIDE_LEFT = 0x200u,
70 1, // AUDIO_CHANNEL_OUT_SIDE_RIGHT = 0x400u,
71 2, // AUDIO_CHANNEL_OUT_TOP_CENTER = 0x800u,
72 0, // AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT = 0x1000u,
73 2, // AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER = 0x2000u,
74 1, // AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT = 0x4000u,
75 0, // AUDIO_CHANNEL_OUT_TOP_BACK_LEFT = 0x8000u,
76 2, // AUDIO_CHANNEL_OUT_TOP_BACK_CENTER = 0x10000u,
77 1, // AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT = 0x20000u,
78 0, // AUDIO_CHANNEL_OUT_TOP_SIDE_LEFT = 0x40000u,
79 1, // AUDIO_CHANNEL_OUT_TOP_SIDE_RIGHT = 0x80000u,
80 };
81
82 mSides.resize(mChannelCount);
83 for (unsigned i = 0, channel = channelMask; channel != 0; ++i) {
84 const int index = __builtin_ctz(channel);
85 if (index < std::size(sideFromChannel)) {
86 mSides[i] = sideFromChannel[index];
87 } else {
88 mSides[i] = 2; // consider center
89 }
90 channel &= ~(1 << index);
91 }
92 setBalance(balance); // recompute balance
93 }
94
process(float * buffer,size_t frames)95 void Balance::process(float *buffer, size_t frames)
96 {
97 if (mBalance == 0.f || mChannelCount < 2) {
98 return;
99 }
100
101 if (mRamp) {
102 if (mRampVolumes.size() != mVolumes.size()) {
103 // If mRampVolumes is empty, we do not ramp in this process() but directly
104 // apply the existing mVolumes. We save the balance and volume state here
105 // and fall through to non-ramping code below. The next process() will ramp if needed.
106 mRampBalance = mBalance;
107 mRampVolumes = mVolumes;
108 } else if (mRampBalance != mBalance) {
109 if (frames > 0) {
110 std::vector<float> mDeltas(mVolumes.size());
111 const float r = 1.f / frames;
112 for (size_t j = 0; j < mChannelCount; ++j) {
113 mDeltas[j] = (mVolumes[j] - mRampVolumes[j]) * r;
114 }
115
116 // ramped balance
117 for (size_t i = 0; i < frames; ++i) {
118 const float findex = i;
119 for (size_t j = 0; j < mChannelCount; ++j) { // better precision: delta * i
120 *buffer++ *= mRampVolumes[j] + mDeltas[j] * findex;
121 }
122 }
123 }
124 mRampBalance = mBalance;
125 mRampVolumes = mVolumes;
126 return;
127 }
128 // fall through
129 }
130
131 // non-ramped balance
132 for (size_t i = 0; i < frames; ++i) {
133 for (size_t j = 0; j < mChannelCount; ++j) {
134 *buffer++ *= mVolumes[j];
135 }
136 }
137 }
138
computeStereoBalance(float balance,float * left,float * right) const139 void Balance::computeStereoBalance(float balance, float *left, float *right) const
140 {
141 if (balance > 0.f) {
142 *left = mCurve(1.f - balance);
143 *right = 1.f;
144 } else if (balance < 0.f) {
145 *left = 1.f;
146 *right = mCurve(1.f + balance);
147 } else {
148 *left = 1.f;
149 *right = 1.f;
150 }
151
152 // Functionally:
153 // *left = balance > 0.f ? mCurve(1.f - balance) : 1.f;
154 // *right = balance < 0.f ? mCurve(1.f + balance) : 1.f;
155 }
156
toString() const157 std::string Balance::toString() const
158 {
159 std::stringstream ss;
160 ss << "balance " << mBalance << " channelCount " << mChannelCount << " volumes:";
161 for (float volume : mVolumes) {
162 ss << " " << volume;
163 }
164 // we do not show mSides, which is only valid for channel position masks.
165 return ss.str();
166 }
167
setBalance(float balance)168 void Balance::setBalance(float balance)
169 {
170 if (mBalance == balance // no change
171 || isnan(balance) || fabs(balance) > 1.f) { // balance out of range
172 return;
173 }
174
175 mBalance = balance;
176
177 if (mChannelCount < 2) { // if channel count is 1, mVolumes[0] is already set to 1.f
178 return; // and if channel count < 2, we don't do anything in process().
179 }
180
181 // Handle the common cases:
182 // stereo and channel index masks only affect the first two channels as left and right.
183 if (mChannelMask == AUDIO_CHANNEL_OUT_STEREO
184 || audio_channel_mask_get_representation(mChannelMask)
185 == AUDIO_CHANNEL_REPRESENTATION_INDEX) {
186 computeStereoBalance(balance, &mVolumes[0], &mVolumes[1]);
187 return;
188 }
189
190 // For position masks with more than 2 channels, we consider which side the
191 // speaker position is on to figure the volume used.
192 float balanceVolumes[3]; // left, right, center
193 computeStereoBalance(balance, &balanceVolumes[0], &balanceVolumes[1]);
194 balanceVolumes[2] = 1.f; // center TODO: consider center scaling.
195
196 for (size_t i = 0; i < mVolumes.size(); ++i) {
197 mVolumes[i] = balanceVolumes[mSides[i]];
198 }
199 }
200
201 } // namespace android::audio_utils
202