• 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     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