| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 1 | /* | 
 | 2 |  * Copyright 2017 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 | #ifndef ANDROID_VOLUME_SHAPER_H | 
 | 18 | #define ANDROID_VOLUME_SHAPER_H | 
 | 19 |  | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 20 | #include <cmath> | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 21 | #include <list> | 
 | 22 | #include <math.h> | 
 | 23 | #include <sstream> | 
 | 24 |  | 
 | 25 | #include <binder/Parcel.h> | 
 | 26 | #include <media/Interpolator.h> | 
 | 27 | #include <utils/Mutex.h> | 
 | 28 | #include <utils/RefBase.h> | 
 | 29 |  | 
 | 30 | #pragma push_macro("LOG_TAG") | 
 | 31 | #undef LOG_TAG | 
 | 32 | #define LOG_TAG "VolumeShaper" | 
 | 33 |  | 
 | 34 | // turn on VolumeShaper logging | 
| Colin Cross | 4e39999 | 2017-04-27 16:15:51 -0700 | [diff] [blame] | 35 | #define VS_LOGGING 0 | 
 | 36 | #define VS_LOG(...) ALOGD_IF(VS_LOGGING, __VA_ARGS__) | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 37 |  | 
 | 38 | namespace android { | 
 | 39 |  | 
| Ivan Lozano | 8cf3a07 | 2017-08-09 09:01:33 -0700 | [diff] [blame] | 40 | namespace media { | 
 | 41 |  | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 42 | // The native VolumeShaper class mirrors the java VolumeShaper class; | 
 | 43 | // in addition, the native class contains implementation for actual operation. | 
 | 44 | // | 
 | 45 | // VolumeShaper methods are not safe for multiple thread access. | 
 | 46 | // Use VolumeHandler for thread-safe encapsulation of multiple VolumeShapers. | 
 | 47 | // | 
 | 48 | // Classes below written are to avoid naked pointers so there are no | 
 | 49 | // explicit destructors required. | 
 | 50 |  | 
 | 51 | class VolumeShaper { | 
 | 52 | public: | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 53 |     // S and T are like template typenames (matching the Interpolator<S, T>) | 
 | 54 |     using S = float; // time type | 
 | 55 |     using T = float; // volume type | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 56 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 57 | // Curve and dimension information | 
 | 58 | // TODO: member static const or constexpr float initialization not permitted in C++11 | 
 | 59 | #define MIN_CURVE_TIME    0.f  // type S: start of VolumeShaper curve (normalized) | 
 | 60 | #define MAX_CURVE_TIME    1.f  // type S: end of VolumeShaper curve (normalized) | 
 | 61 | #define MIN_LINEAR_VOLUME 0.f  // type T: silence / mute audio | 
 | 62 | #define MAX_LINEAR_VOLUME 1.f  // type T: max volume, unity gain | 
 | 63 | #define MAX_LOG_VOLUME    0.f  // type T: max volume, unity gain in dBFS | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 64 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 65 |     /* kSystemVolumeShapersMax is the maximum number of system VolumeShapers. | 
 | 66 |      * Each system VolumeShapers has a predefined Id, which ranges from 0 | 
 | 67 |      * to kSystemVolumeShapersMax - 1 and is unique for its usage. | 
 | 68 |      * | 
 | 69 |      * "1" is reserved for system ducking. | 
 | 70 |      */ | 
 | 71 |     static const int kSystemVolumeShapersMax = 16; | 
 | 72 |  | 
 | 73 |     /* kUserVolumeShapersMax is the maximum number of application | 
 | 74 |      * VolumeShapers for a player/track.  Application VolumeShapers are | 
 | 75 |      * assigned on creation by the client, and have Ids ranging | 
 | 76 |      * from kSystemVolumeShapersMax to INT32_MAX. | 
 | 77 |      * | 
 | 78 |      * The number of user/application volume shapers is independent to the | 
 | 79 |      * system volume shapers. If an application tries to create more than | 
 | 80 |      * kUserVolumeShapersMax to a player, then the apply() will fail. | 
 | 81 |      * This prevents exhausting server side resources by a potentially malicious | 
 | 82 |      * application. | 
 | 83 |      */ | 
 | 84 |     static const int kUserVolumeShapersMax = 16; | 
 | 85 |  | 
 | 86 |     /* VolumeShaper::Status is equivalent to status_t if negative | 
 | 87 |      * but if non-negative represents the id operated on. | 
 | 88 |      * It must be expressible as an int32_t for binder purposes. | 
 | 89 |      */ | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 90 |     using Status = status_t; | 
 | 91 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 92 |     // Local definition for clamp as std::clamp is included in C++17 only. | 
 | 93 |     // TODO: use the std::clamp version when Android build uses C++17. | 
 | 94 |     template<typename R> | 
 | 95 |     static constexpr const R &clamp(const R &v, const R &lo, const R &hi) { | 
 | 96 |         return (v < lo) ? lo : (hi < v) ? hi : v; | 
 | 97 |     } | 
 | 98 |  | 
 | 99 |     /* VolumeShaper.Configuration derives from the Interpolator class and adds | 
 | 100 |      * parameters relating to the volume shape. | 
 | 101 |      * | 
 | 102 |      * This parallels the Java implementation and the enums must match. | 
 | 103 |      * See "frameworks/base/media/java/android/media/VolumeShaper.java" for | 
 | 104 |      * details on the Java implementation. | 
 | 105 |      */ | 
| Ivan Lozano | 8cf3a07 | 2017-08-09 09:01:33 -0700 | [diff] [blame] | 106 |     class Configuration : public Interpolator<S, T>, public RefBase, public Parcelable { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 107 |     public: | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 108 |         // Must match with VolumeShaper.java in frameworks/base. | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 109 |         enum Type : int32_t { | 
 | 110 |             TYPE_ID, | 
 | 111 |             TYPE_SCALE, | 
 | 112 |         }; | 
 | 113 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 114 |         // Must match with VolumeShaper.java in frameworks/base. | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 115 |         enum OptionFlag : int32_t { | 
 | 116 |             OPTION_FLAG_NONE           = 0, | 
 | 117 |             OPTION_FLAG_VOLUME_IN_DBFS = (1 << 0), | 
 | 118 |             OPTION_FLAG_CLOCK_TIME     = (1 << 1), | 
 | 119 |  | 
 | 120 |             OPTION_FLAG_ALL            = (OPTION_FLAG_VOLUME_IN_DBFS | OPTION_FLAG_CLOCK_TIME), | 
 | 121 |         }; | 
 | 122 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 123 |         // Bring from base class; must match with VolumeShaper.java in frameworks/base. | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 124 |         using InterpolatorType = Interpolator<S, T>::InterpolatorType; | 
 | 125 |  | 
 | 126 |         Configuration() | 
 | 127 |             : Interpolator<S, T>() | 
| Colin Cross | 11280a1 | 2017-05-02 10:32:56 -0700 | [diff] [blame] | 128 |             , RefBase() | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 129 |             , mType(TYPE_SCALE) | 
| Colin Cross | 4e39999 | 2017-04-27 16:15:51 -0700 | [diff] [blame] | 130 |             , mId(-1) | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 131 |             , mOptionFlags(OPTION_FLAG_NONE) | 
| Colin Cross | 4e39999 | 2017-04-27 16:15:51 -0700 | [diff] [blame] | 132 |             , mDurationMs(1000.) { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 133 |         } | 
 | 134 |  | 
| Andy Hung | 7d712bb | 2017-04-20 14:23:41 -0700 | [diff] [blame] | 135 |         explicit Configuration(const Configuration &configuration) | 
| Andy Hung | 10cbff1 | 2017-02-21 17:30:14 -0800 | [diff] [blame] | 136 |             : Interpolator<S, T>(*static_cast<const Interpolator<S, T> *>(&configuration)) | 
| Colin Cross | 11280a1 | 2017-05-02 10:32:56 -0700 | [diff] [blame] | 137 |             , RefBase() | 
| Andy Hung | 10cbff1 | 2017-02-21 17:30:14 -0800 | [diff] [blame] | 138 |             , mType(configuration.mType) | 
| Colin Cross | 4e39999 | 2017-04-27 16:15:51 -0700 | [diff] [blame] | 139 |             , mId(configuration.mId) | 
| Andy Hung | 10cbff1 | 2017-02-21 17:30:14 -0800 | [diff] [blame] | 140 |             , mOptionFlags(configuration.mOptionFlags) | 
| Colin Cross | 4e39999 | 2017-04-27 16:15:51 -0700 | [diff] [blame] | 141 |             , mDurationMs(configuration.mDurationMs) { | 
| Andy Hung | 10cbff1 | 2017-02-21 17:30:14 -0800 | [diff] [blame] | 142 |         } | 
 | 143 |  | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 144 |         Type getType() const { | 
 | 145 |             return mType; | 
 | 146 |         } | 
 | 147 |  | 
 | 148 |         status_t setType(Type type) { | 
 | 149 |             switch (type) { | 
 | 150 |             case TYPE_ID: | 
 | 151 |             case TYPE_SCALE: | 
 | 152 |                 mType = type; | 
 | 153 |                 return NO_ERROR; | 
 | 154 |             default: | 
 | 155 |                 ALOGE("invalid Type: %d", type); | 
 | 156 |                 return BAD_VALUE; | 
 | 157 |             } | 
 | 158 |         } | 
 | 159 |  | 
 | 160 |         OptionFlag getOptionFlags() const { | 
 | 161 |             return mOptionFlags; | 
 | 162 |         } | 
 | 163 |  | 
 | 164 |         status_t setOptionFlags(OptionFlag optionFlags) { | 
 | 165 |             if ((optionFlags & ~OPTION_FLAG_ALL) != 0) { | 
 | 166 |                 ALOGE("optionFlags has invalid bits: %#x", optionFlags); | 
 | 167 |                 return BAD_VALUE; | 
 | 168 |             } | 
 | 169 |             mOptionFlags = optionFlags; | 
 | 170 |             return NO_ERROR; | 
 | 171 |         } | 
 | 172 |  | 
 | 173 |         double getDurationMs() const { | 
 | 174 |             return mDurationMs; | 
 | 175 |         } | 
 | 176 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 177 |         status_t setDurationMs(double durationMs) { | 
 | 178 |             if (durationMs > 0.) { | 
 | 179 |                 mDurationMs = durationMs; | 
 | 180 |                 return NO_ERROR; | 
 | 181 |             } | 
 | 182 |             // zero, negative, or nan. These values not possible from Java. | 
 | 183 |             return BAD_VALUE; | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 184 |         } | 
 | 185 |  | 
 | 186 |         int32_t getId() const { | 
 | 187 |             return mId; | 
 | 188 |         } | 
 | 189 |  | 
 | 190 |         void setId(int32_t id) { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 191 |             // We permit a negative id here (representing invalid). | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 192 |             mId = id; | 
 | 193 |         } | 
 | 194 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 195 |         /* Adjust the volume to be in linear range from MIN_LINEAR_VOLUME to MAX_LINEAR_VOLUME | 
 | 196 |          * and compensate for log dbFS volume as needed. | 
 | 197 |          */ | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 198 |         T adjustVolume(T volume) const { | 
 | 199 |             if ((getOptionFlags() & OPTION_FLAG_VOLUME_IN_DBFS) != 0) { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 200 |                 const T out = powf(10.f, volume / 10.f); | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 201 |                 VS_LOG("in: %f  out: %f", volume, out); | 
 | 202 |                 volume = out; | 
 | 203 |             } | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 204 |             return clamp(volume, MIN_LINEAR_VOLUME /* lo */, MAX_LINEAR_VOLUME /* hi */); | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 205 |         } | 
 | 206 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 207 |         /* Check if the existing curve is valid. | 
 | 208 |          */ | 
 | 209 |         status_t checkCurve() const { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 210 |             if (mType == TYPE_ID) return NO_ERROR; | 
 | 211 |             if (this->size() < 2) { | 
 | 212 |                 ALOGE("curve must have at least 2 points"); | 
 | 213 |                 return BAD_VALUE; | 
 | 214 |             } | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 215 |             if (first().first != MIN_CURVE_TIME || last().first != MAX_CURVE_TIME) { | 
 | 216 |                 ALOGE("curve must start at MIN_CURVE_TIME and end at MAX_CURVE_TIME"); | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 217 |                 return BAD_VALUE; | 
 | 218 |             } | 
 | 219 |             if ((getOptionFlags() & OPTION_FLAG_VOLUME_IN_DBFS) != 0) { | 
 | 220 |                 for (const auto &pt : *this) { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 221 |                     if (!(pt.second <= MAX_LOG_VOLUME) /* handle nan */) { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 222 |                         ALOGE("positive volume dbFS"); | 
 | 223 |                         return BAD_VALUE; | 
 | 224 |                     } | 
 | 225 |                 } | 
 | 226 |             } else { | 
 | 227 |                 for (const auto &pt : *this) { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 228 |                     if (!(pt.second >= MIN_LINEAR_VOLUME) | 
 | 229 |                             || !(pt.second <= MAX_LINEAR_VOLUME) /* handle nan */) { | 
 | 230 |                         ALOGE("volume < MIN_LINEAR_VOLUME or > MAX_LINEAR_VOLUME"); | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 231 |                         return BAD_VALUE; | 
 | 232 |                     } | 
 | 233 |                 } | 
 | 234 |             } | 
 | 235 |             return NO_ERROR; | 
 | 236 |         } | 
 | 237 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 238 |         /* Clamps the volume curve in the configuration to | 
 | 239 |          * the valid range for log or linear scale. | 
 | 240 |          */ | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 241 |         void clampVolume() { | 
 | 242 |             if ((mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0) { | 
 | 243 |                 for (auto it = this->begin(); it != this->end(); ++it) { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 244 |                     if (!(it->second <= MAX_LOG_VOLUME) /* handle nan */) { | 
 | 245 |                         it->second = MAX_LOG_VOLUME; | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 246 |                     } | 
 | 247 |                 } | 
 | 248 |             } else { | 
 | 249 |                 for (auto it = this->begin(); it != this->end(); ++it) { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 250 |                     if (!(it->second >= MIN_LINEAR_VOLUME) /* handle nan */) { | 
 | 251 |                         it->second = MIN_LINEAR_VOLUME; | 
 | 252 |                     } else if (!(it->second <= MAX_LINEAR_VOLUME)) { | 
 | 253 |                         it->second = MAX_LINEAR_VOLUME; | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 254 |                     } | 
 | 255 |                 } | 
 | 256 |             } | 
 | 257 |         } | 
 | 258 |  | 
 | 259 |         /* scaleToStartVolume() is used to set the start volume of a | 
 | 260 |          * new VolumeShaper curve, when replacing one VolumeShaper | 
 | 261 |          * with another using the "join" (volume match) option. | 
 | 262 |          * | 
 | 263 |          * It works best for monotonic volume ramps or ducks. | 
 | 264 |          */ | 
 | 265 |         void scaleToStartVolume(T volume) { | 
 | 266 |             if (this->size() < 2) { | 
 | 267 |                 return; | 
 | 268 |             } | 
 | 269 |             const T startVolume = first().second; | 
 | 270 |             const T endVolume = last().second; | 
 | 271 |             if (endVolume == startVolume) { | 
 | 272 |                 // match with linear ramp | 
 | 273 |                 const T offset = volume - startVolume; | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 274 |                 static const T scale =  1.f / (MAX_CURVE_TIME - MIN_CURVE_TIME); // nominally 1.f | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 275 |                 for (auto it = this->begin(); it != this->end(); ++it) { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 276 |                     it->second = it->second + offset * (MAX_CURVE_TIME - it->first) * scale; | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 277 |                 } | 
 | 278 |             } else { | 
 | 279 |                 const T  scale = (volume - endVolume) / (startVolume - endVolume); | 
 | 280 |                 for (auto it = this->begin(); it != this->end(); ++it) { | 
 | 281 |                     it->second = scale * (it->second - endVolume) + endVolume; | 
 | 282 |                 } | 
 | 283 |             } | 
 | 284 |             clampVolume(); | 
 | 285 |         } | 
 | 286 |  | 
| Andy Hung | 7d712bb | 2017-04-20 14:23:41 -0700 | [diff] [blame] | 287 |         // The parcel layout must match VolumeShaper.java | 
| Ivan Lozano | 8cf3a07 | 2017-08-09 09:01:33 -0700 | [diff] [blame] | 288 |         status_t writeToParcel(Parcel *parcel) const override { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 289 |             if (parcel == nullptr) return BAD_VALUE; | 
 | 290 |             return parcel->writeInt32((int32_t)mType) | 
 | 291 |                     ?: parcel->writeInt32(mId) | 
 | 292 |                     ?: mType == TYPE_ID | 
 | 293 |                         ? NO_ERROR | 
 | 294 |                         : parcel->writeInt32((int32_t)mOptionFlags) | 
 | 295 |                             ?: parcel->writeDouble(mDurationMs) | 
 | 296 |                             ?: Interpolator<S, T>::writeToParcel(parcel); | 
 | 297 |         } | 
 | 298 |  | 
| Ivan Lozano | 8cf3a07 | 2017-08-09 09:01:33 -0700 | [diff] [blame] | 299 |         status_t readFromParcel(const Parcel *parcel) override { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 300 |             int32_t type, optionFlags; | 
| Ivan Lozano | 8cf3a07 | 2017-08-09 09:01:33 -0700 | [diff] [blame] | 301 |             return parcel->readInt32(&type) | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 302 |                     ?: setType((Type)type) | 
| Ivan Lozano | 8cf3a07 | 2017-08-09 09:01:33 -0700 | [diff] [blame] | 303 |                     ?: parcel->readInt32(&mId) | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 304 |                     ?: mType == TYPE_ID | 
 | 305 |                         ? NO_ERROR | 
| Ivan Lozano | 8cf3a07 | 2017-08-09 09:01:33 -0700 | [diff] [blame] | 306 |                         : parcel->readInt32(&optionFlags) | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 307 |                             ?: setOptionFlags((OptionFlag)optionFlags) | 
| Ivan Lozano | 8cf3a07 | 2017-08-09 09:01:33 -0700 | [diff] [blame] | 308 |                             ?: parcel->readDouble(&mDurationMs) | 
 | 309 |                             ?: Interpolator<S, T>::readFromParcel(*parcel) | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 310 |                             ?: checkCurve(); | 
 | 311 |         } | 
 | 312 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 313 |         // Returns a string for debug printing. | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 314 |         std::string toString() const { | 
 | 315 |             std::stringstream ss; | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 316 |             ss << "VolumeShaper::Configuration{mType=" << static_cast<int32_t>(mType); | 
 | 317 |             ss << ", mId=" << mId; | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 318 |             if (mType != TYPE_ID) { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 319 |                 ss << ", mOptionFlags=" << static_cast<int32_t>(mOptionFlags); | 
 | 320 |                 ss << ", mDurationMs=" << mDurationMs; | 
 | 321 |                 ss << ", " << Interpolator<S, T>::toString().c_str(); | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 322 |             } | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 323 |             ss << "}"; | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 324 |             return ss.str(); | 
 | 325 |         } | 
 | 326 |  | 
 | 327 |     private: | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 328 |         Type mType;              // type of configuration | 
 | 329 |         int32_t mId;             // A valid id is >= 0. | 
 | 330 |         OptionFlag mOptionFlags; // option flags for the configuration. | 
 | 331 |         double mDurationMs;      // duration, must be > 0; default is 1000 ms. | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 332 |     }; // Configuration | 
 | 333 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 334 |     /* VolumeShaper::Operation expresses an operation to perform on the | 
 | 335 |      * configuration (either explicitly specified or an id). | 
 | 336 |      * | 
 | 337 |      * This parallels the Java implementation and the enums must match. | 
 | 338 |      * See "frameworks/base/media/java/android/media/VolumeShaper.java" for | 
 | 339 |      * details on the Java implementation. | 
 | 340 |      */ | 
| Ivan Lozano | 8cf3a07 | 2017-08-09 09:01:33 -0700 | [diff] [blame] | 341 |     class Operation : public RefBase, public Parcelable { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 342 |     public: | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 343 |         // Must match with VolumeShaper.java. | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 344 |         enum Flag : int32_t { | 
 | 345 |             FLAG_NONE      = 0, | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 346 |             FLAG_REVERSE   = (1 << 0), // the absence of this indicates "play" | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 347 |             FLAG_TERMINATE = (1 << 1), | 
 | 348 |             FLAG_JOIN      = (1 << 2), | 
 | 349 |             FLAG_DELAY     = (1 << 3), | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 350 |             FLAG_CREATE_IF_NECESSARY = (1 << 4), | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 351 |  | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 352 |             FLAG_ALL       = (FLAG_REVERSE | FLAG_TERMINATE | FLAG_JOIN | FLAG_DELAY | 
 | 353 |                             | FLAG_CREATE_IF_NECESSARY), | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 354 |         }; | 
 | 355 |  | 
 | 356 |         Operation() | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 357 |             : Operation(FLAG_NONE, -1 /* replaceId */) { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 358 |         } | 
 | 359 |  | 
| Andy Hung | 7d712bb | 2017-04-20 14:23:41 -0700 | [diff] [blame] | 360 |         Operation(Flag flags, int replaceId) | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 361 |             : Operation(flags, replaceId, std::numeric_limits<S>::quiet_NaN() /* xOffset */) { | 
 | 362 |         } | 
 | 363 |  | 
| Andy Hung | 7d712bb | 2017-04-20 14:23:41 -0700 | [diff] [blame] | 364 |         explicit Operation(const Operation &operation) | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 365 |             : Operation(operation.mFlags, operation.mReplaceId, operation.mXOffset) { | 
 | 366 |         } | 
 | 367 |  | 
| Andy Hung | 7d712bb | 2017-04-20 14:23:41 -0700 | [diff] [blame] | 368 |         explicit Operation(const sp<Operation> &operation) | 
 | 369 |             : Operation(*operation.get()) { | 
 | 370 |         } | 
 | 371 |  | 
 | 372 |         Operation(Flag flags, int replaceId, S xOffset) | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 373 |             : mFlags(flags) | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 374 |             , mReplaceId(replaceId) | 
 | 375 |             , mXOffset(xOffset) { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 376 |         } | 
 | 377 |  | 
 | 378 |         int32_t getReplaceId() const { | 
 | 379 |             return mReplaceId; | 
 | 380 |         } | 
 | 381 |  | 
 | 382 |         void setReplaceId(int32_t replaceId) { | 
 | 383 |             mReplaceId = replaceId; | 
 | 384 |         } | 
 | 385 |  | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 386 |         S getXOffset() const { | 
 | 387 |             return mXOffset; | 
 | 388 |         } | 
 | 389 |  | 
 | 390 |         void setXOffset(S xOffset) { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 391 |             mXOffset = clamp(xOffset, MIN_CURVE_TIME /* lo */, MAX_CURVE_TIME /* hi */); | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 392 |         } | 
 | 393 |  | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 394 |         Flag getFlags() const { | 
 | 395 |             return mFlags; | 
 | 396 |         } | 
 | 397 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 398 |         /* xOffset is the position on the volume curve and may go backwards | 
 | 399 |          * if you are in reverse mode. This must be in the range from | 
 | 400 |          * [MIN_CURVE_TIME, MAX_CURVE_TIME]. | 
 | 401 |          * | 
 | 402 |          * normalizedTime always increases as time or framecount increases. | 
 | 403 |          * normalizedTime is nominally from MIN_CURVE_TIME to MAX_CURVE_TIME when | 
 | 404 |          * running through the curve, but could be outside this range afterwards. | 
 | 405 |          * If you are reversing, this means the position on the curve, or xOffset, | 
 | 406 |          * is computed as MAX_CURVE_TIME - normalizedTime, clamped to | 
 | 407 |          * [MIN_CURVE_TIME, MAX_CURVE_TIME]. | 
 | 408 |          */ | 
 | 409 |         void setNormalizedTime(S normalizedTime) { | 
 | 410 |             setXOffset((mFlags & FLAG_REVERSE) != 0 | 
 | 411 |                     ? MAX_CURVE_TIME - normalizedTime : normalizedTime); | 
 | 412 |         } | 
 | 413 |  | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 414 |         status_t setFlags(Flag flags) { | 
 | 415 |             if ((flags & ~FLAG_ALL) != 0) { | 
 | 416 |                 ALOGE("flags has invalid bits: %#x", flags); | 
 | 417 |                 return BAD_VALUE; | 
 | 418 |             } | 
 | 419 |             mFlags = flags; | 
 | 420 |             return NO_ERROR; | 
 | 421 |         } | 
 | 422 |  | 
| Ivan Lozano | 8cf3a07 | 2017-08-09 09:01:33 -0700 | [diff] [blame] | 423 |         status_t writeToParcel(Parcel *parcel) const override { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 424 |             if (parcel == nullptr) return BAD_VALUE; | 
 | 425 |             return parcel->writeInt32((int32_t)mFlags) | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 426 |                     ?: parcel->writeInt32(mReplaceId) | 
 | 427 |                     ?: parcel->writeFloat(mXOffset); | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 428 |         } | 
 | 429 |  | 
| Ivan Lozano | 8cf3a07 | 2017-08-09 09:01:33 -0700 | [diff] [blame] | 430 |         status_t readFromParcel(const Parcel *parcel) override { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 431 |             int32_t flags; | 
| Ivan Lozano | 8cf3a07 | 2017-08-09 09:01:33 -0700 | [diff] [blame] | 432 |             return parcel->readInt32(&flags) | 
 | 433 |                     ?: parcel->readInt32(&mReplaceId) | 
 | 434 |                     ?: parcel->readFloat(&mXOffset) | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 435 |                     ?: setFlags((Flag)flags); | 
 | 436 |         } | 
 | 437 |  | 
 | 438 |         std::string toString() const { | 
 | 439 |             std::stringstream ss; | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 440 |             ss << "VolumeShaper::Operation{mFlags=" << static_cast<int32_t>(mFlags) ; | 
 | 441 |             ss << ", mReplaceId=" << mReplaceId; | 
 | 442 |             ss << ", mXOffset=" << mXOffset; | 
 | 443 |             ss << "}"; | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 444 |             return ss.str(); | 
 | 445 |         } | 
 | 446 |  | 
 | 447 |     private: | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 448 |         Flag mFlags;        // operation to do | 
 | 449 |         int32_t mReplaceId; // if >= 0 the id to remove in a replace operation. | 
 | 450 |         S mXOffset;         // position in the curve to set if a valid number (not nan) | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 451 |     }; // Operation | 
 | 452 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 453 |     /* VolumeShaper.State is returned when requesting the last | 
 | 454 |      * state of the VolumeShaper. | 
 | 455 |      * | 
 | 456 |      * This parallels the Java implementation. | 
 | 457 |      * See "frameworks/base/media/java/android/media/VolumeShaper.java" for | 
 | 458 |      * details on the Java implementation. | 
 | 459 |      */ | 
| Ivan Lozano | 8cf3a07 | 2017-08-09 09:01:33 -0700 | [diff] [blame] | 460 |     class State : public RefBase, public Parcelable { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 461 |     public: | 
| Andy Hung | 7d712bb | 2017-04-20 14:23:41 -0700 | [diff] [blame] | 462 |         State(T volume, S xOffset) | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 463 |             : mVolume(volume) | 
 | 464 |             , mXOffset(xOffset) { | 
 | 465 |         } | 
 | 466 |  | 
 | 467 |         State() | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 468 |             : State(NAN, NAN) { } | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 469 |  | 
 | 470 |         T getVolume() const { | 
 | 471 |             return mVolume; | 
 | 472 |         } | 
 | 473 |  | 
 | 474 |         void setVolume(T volume) { | 
 | 475 |             mVolume = volume; | 
 | 476 |         } | 
 | 477 |  | 
 | 478 |         S getXOffset() const { | 
 | 479 |             return mXOffset; | 
 | 480 |         } | 
 | 481 |  | 
 | 482 |         void setXOffset(S xOffset) { | 
 | 483 |             mXOffset = xOffset; | 
 | 484 |         } | 
 | 485 |  | 
| Ivan Lozano | 8cf3a07 | 2017-08-09 09:01:33 -0700 | [diff] [blame] | 486 |         status_t writeToParcel(Parcel *parcel) const override { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 487 |             if (parcel == nullptr) return BAD_VALUE; | 
 | 488 |             return parcel->writeFloat(mVolume) | 
 | 489 |                     ?: parcel->writeFloat(mXOffset); | 
 | 490 |         } | 
 | 491 |  | 
| Ivan Lozano | 8cf3a07 | 2017-08-09 09:01:33 -0700 | [diff] [blame] | 492 |         status_t readFromParcel(const Parcel *parcel) override { | 
 | 493 |             return parcel->readFloat(&mVolume) | 
 | 494 |                      ?: parcel->readFloat(&mXOffset); | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 495 |         } | 
 | 496 |  | 
 | 497 |         std::string toString() const { | 
 | 498 |             std::stringstream ss; | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 499 |             ss << "VolumeShaper::State{mVolume=" << mVolume; | 
 | 500 |             ss << ", mXOffset=" << mXOffset; | 
 | 501 |             ss << "}"; | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 502 |             return ss.str(); | 
 | 503 |         } | 
 | 504 |  | 
 | 505 |     private: | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 506 |         T mVolume;   // linear volume in the range MIN_LINEAR_VOLUME to MAX_LINEAR_VOLUME | 
 | 507 |         S mXOffset;  // position on curve expressed from MIN_CURVE_TIME to MAX_CURVE_TIME | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 508 |     }; // State | 
 | 509 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 510 |     // Internal helper class to do an affine transform for time and amplitude scaling. | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 511 |     template <typename R> | 
 | 512 |     class Translate { | 
 | 513 |     public: | 
 | 514 |         Translate() | 
 | 515 |             : mOffset(0) | 
 | 516 |             , mScale(1) { | 
 | 517 |         } | 
 | 518 |  | 
 | 519 |         R getOffset() const { | 
 | 520 |             return mOffset; | 
 | 521 |         } | 
 | 522 |  | 
 | 523 |         void setOffset(R offset) { | 
 | 524 |             mOffset = offset; | 
 | 525 |         } | 
 | 526 |  | 
 | 527 |         R getScale() const { | 
 | 528 |             return mScale; | 
 | 529 |         } | 
 | 530 |  | 
 | 531 |         void setScale(R scale) { | 
 | 532 |             mScale = scale; | 
 | 533 |         } | 
 | 534 |  | 
 | 535 |         R operator()(R in) const { | 
 | 536 |             return mScale * (in - mOffset); | 
 | 537 |         } | 
 | 538 |  | 
 | 539 |         std::string toString() const { | 
 | 540 |             std::stringstream ss; | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 541 |             ss << "VolumeShaper::Translate{mOffset=" << mOffset; | 
 | 542 |             ss << ", mScale=" << mScale; | 
 | 543 |             ss << "}"; | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 544 |             return ss.str(); | 
 | 545 |         } | 
 | 546 |  | 
 | 547 |     private: | 
 | 548 |         R mOffset; | 
 | 549 |         R mScale; | 
 | 550 |     }; // Translate | 
 | 551 |  | 
 | 552 |     static int64_t convertTimespecToUs(const struct timespec &tv) | 
 | 553 |     { | 
| Chih-Hung Hsieh | 21b9616 | 2018-12-11 13:48:32 -0800 | [diff] [blame^] | 554 |         return tv.tv_sec * 1000000LL + tv.tv_nsec / 1000; | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 555 |     } | 
 | 556 |  | 
 | 557 |     // current monotonic time in microseconds. | 
 | 558 |     static int64_t getNowUs() | 
 | 559 |     { | 
 | 560 |         struct timespec tv; | 
 | 561 |         if (clock_gettime(CLOCK_MONOTONIC, &tv) != 0) { | 
 | 562 |             return 0; // system is really sick, just return 0 for consistency. | 
 | 563 |         } | 
 | 564 |         return convertTimespecToUs(tv); | 
 | 565 |     } | 
 | 566 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 567 |     /* Native implementation of VolumeShaper.  This is NOT mirrored | 
 | 568 |      * on the Java side, so we don't need to mimic Java side layout | 
 | 569 |      * and data; furthermore, this isn't refcounted as a "RefBase" object. | 
 | 570 |      * | 
 | 571 |      * Since we pass configuration and operation as shared pointers (like | 
 | 572 |      * Java) there is a potential risk that the caller may modify | 
 | 573 |      * these after delivery. | 
 | 574 |      */ | 
| Andy Hung | 7d712bb | 2017-04-20 14:23:41 -0700 | [diff] [blame] | 575 |     VolumeShaper( | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 576 |             const sp<VolumeShaper::Configuration> &configuration, | 
 | 577 |             const sp<VolumeShaper::Operation> &operation) | 
 | 578 |         : mConfiguration(configuration) // we do not make a copy | 
 | 579 |         , mOperation(operation)         // ditto | 
 | 580 |         , mStartFrame(-1) | 
 | 581 |         , mLastVolume(T(1)) | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 582 |         , mLastXOffset(MIN_CURVE_TIME) | 
 | 583 |         , mDelayXOffset(MIN_CURVE_TIME) { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 584 |         if (configuration.get() != nullptr | 
 | 585 |                 && (getFlags() & VolumeShaper::Operation::FLAG_DELAY) == 0) { | 
 | 586 |             mLastVolume = configuration->first().second; | 
 | 587 |         } | 
 | 588 |     } | 
 | 589 |  | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 590 |     // We allow a null operation here, though VolumeHandler always provides one. | 
 | 591 |     VolumeShaper::Operation::Flag getFlags() const { | 
 | 592 |         return mOperation == nullptr | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 593 |                 ? VolumeShaper::Operation::FLAG_NONE : mOperation->getFlags(); | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 594 |     } | 
 | 595 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 596 |     /* Returns the last volume and xoffset reported to the AudioFlinger. | 
 | 597 |      * If the VolumeShaper has not been started, compute what the volume | 
 | 598 |      * should be based on the initial offset specified. | 
 | 599 |      */ | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 600 |     sp<VolumeShaper::State> getState() const { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 601 |         if (!isStarted()) { | 
 | 602 |             const T volume = computeVolumeFromXOffset(mDelayXOffset); | 
 | 603 |             VS_LOG("delayed VolumeShaper, using cached offset:%f for volume:%f", | 
 | 604 |                     mDelayXOffset, volume); | 
 | 605 |             return new VolumeShaper::State(volume, mDelayXOffset); | 
 | 606 |         } else { | 
 | 607 |             return new VolumeShaper::State(mLastVolume, mLastXOffset); | 
 | 608 |         } | 
 | 609 |     } | 
 | 610 |  | 
 | 611 |     S getDelayXOffset() const { | 
 | 612 |         return mDelayXOffset; | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 613 |     } | 
 | 614 |  | 
 | 615 |     void setDelayXOffset(S xOffset) { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 616 |         mDelayXOffset = clamp(xOffset, MIN_CURVE_TIME /* lo */, MAX_CURVE_TIME /* hi */); | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 617 |     } | 
 | 618 |  | 
| Andy Hung | 39399b6 | 2017-04-21 15:07:45 -0700 | [diff] [blame] | 619 |     bool isStarted() const { | 
 | 620 |         return mStartFrame >= 0; | 
 | 621 |     } | 
 | 622 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 623 |     /* getVolume() updates the last volume/xoffset state so it is not | 
 | 624 |      * const, even though logically it may be viewed as const. | 
 | 625 |      */ | 
| Andy Hung | 10cbff1 | 2017-02-21 17:30:14 -0800 | [diff] [blame] | 626 |     std::pair<T /* volume */, bool /* active */> getVolume( | 
 | 627 |             int64_t trackFrameCount, double trackSampleRate) { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 628 |         if ((getFlags() & VolumeShaper::Operation::FLAG_DELAY) != 0) { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 629 |             // We haven't had PLAY called yet, so just return the value | 
 | 630 |             // as if PLAY were called just now. | 
 | 631 |             VS_LOG("delayed VolumeShaper, using cached offset %f", mDelayXOffset); | 
 | 632 |             const T volume = computeVolumeFromXOffset(mDelayXOffset); | 
 | 633 |             return std::make_pair(volume, false); | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 634 |         } | 
 | 635 |         const bool clockTime = (mConfiguration->getOptionFlags() | 
 | 636 |                 & VolumeShaper::Configuration::OPTION_FLAG_CLOCK_TIME) != 0; | 
 | 637 |         const int64_t frameCount = clockTime ? getNowUs() : trackFrameCount; | 
 | 638 |         const double sampleRate = clockTime ? 1000000 : trackSampleRate; | 
 | 639 |  | 
 | 640 |         if (mStartFrame < 0) { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 641 |             updatePosition(frameCount, sampleRate, mDelayXOffset); | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 642 |             mStartFrame = frameCount; | 
 | 643 |         } | 
 | 644 |         VS_LOG("frameCount: %lld", (long long)frameCount); | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 645 |         const S x = mXTranslate((T)frameCount); | 
 | 646 |         VS_LOG("translation to normalized time: %f", x); | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 647 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 648 |         std::tuple<T /* volume */, S /* position */, bool /* active */> vt = | 
 | 649 |                 computeStateFromNormalizedTime(x); | 
 | 650 |  | 
 | 651 |         mLastVolume = std::get<0>(vt); | 
 | 652 |         mLastXOffset = std::get<1>(vt); | 
 | 653 |         const bool active = std::get<2>(vt); | 
 | 654 |         VS_LOG("rescaled time:%f  volume:%f  xOffset:%f  active:%s", | 
 | 655 |                 x, mLastVolume, mLastXOffset, active ? "true" : "false"); | 
 | 656 |         return std::make_pair(mLastVolume, active); | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 657 |     } | 
 | 658 |  | 
 | 659 |     std::string toString() const { | 
 | 660 |         std::stringstream ss; | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 661 |         ss << "VolumeShaper{mStartFrame=" << mStartFrame; | 
 | 662 |         ss << ", mXTranslate=" << mXTranslate.toString().c_str(); | 
 | 663 |         ss << ", mConfiguration=" << | 
 | 664 |                 (mConfiguration.get() == nullptr | 
 | 665 |                         ? "nullptr" : mConfiguration->toString().c_str()); | 
 | 666 |         ss << ", mOperation=" << | 
 | 667 |                 (mOperation.get() == nullptr | 
 | 668 |                         ? "nullptr" :  mOperation->toString().c_str()); | 
 | 669 |         ss << "}"; | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 670 |         return ss.str(); | 
 | 671 |     } | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 672 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 673 |     Translate<S> mXTranslate; // translation from frames (usec for clock time) to normalized time. | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 674 |     sp<VolumeShaper::Configuration> mConfiguration; | 
 | 675 |     sp<VolumeShaper::Operation> mOperation; | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 676 |  | 
 | 677 | private: | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 678 |     int64_t mStartFrame; // starting frame, non-negative when started (in usec for clock time) | 
 | 679 |     T mLastVolume;       // last computed interpolated volume (y-axis) | 
 | 680 |     S mLastXOffset;      // last computed interpolated xOffset/time (x-axis) | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 681 |     S mDelayXOffset;     // xOffset to use for first invocation of VolumeShaper. | 
 | 682 |  | 
 | 683 |     // Called internally to adjust mXTranslate for first time start. | 
 | 684 |     void updatePosition(int64_t startFrame, double sampleRate, S xOffset) { | 
 | 685 |         double scale = (mConfiguration->last().first - mConfiguration->first().first) | 
 | 686 |                         / (mConfiguration->getDurationMs() * 0.001 * sampleRate); | 
 | 687 |         const double minScale = 1. / static_cast<double>(INT64_MAX); | 
 | 688 |         scale = std::max(scale, minScale); | 
 | 689 |         VS_LOG("update position: scale %lf  frameCount:%lld, sampleRate:%lf, xOffset:%f", | 
 | 690 |                 scale, (long long) startFrame, sampleRate, xOffset); | 
 | 691 |  | 
 | 692 |         S normalizedTime = (getFlags() & VolumeShaper::Operation::FLAG_REVERSE) != 0 ? | 
 | 693 |                 MAX_CURVE_TIME - xOffset : xOffset; | 
 | 694 |         mXTranslate.setOffset(static_cast<float>(static_cast<double>(startFrame) | 
 | 695 |                                                  - static_cast<double>(normalizedTime) / scale)); | 
 | 696 |         mXTranslate.setScale(static_cast<float>(scale)); | 
 | 697 |         VS_LOG("translate: %s", mXTranslate.toString().c_str()); | 
 | 698 |     } | 
 | 699 |  | 
 | 700 |     T computeVolumeFromXOffset(S xOffset) const { | 
 | 701 |         const T unscaledVolume = mConfiguration->findY(xOffset); | 
 | 702 |         const T volume = mConfiguration->adjustVolume(unscaledVolume); // handle log scale | 
 | 703 |         VS_LOG("computeVolumeFromXOffset %f -> %f -> %f", xOffset, unscaledVolume, volume); | 
 | 704 |         return volume; | 
 | 705 |     } | 
 | 706 |  | 
 | 707 |     std::tuple<T /* volume */, S /* position */, bool /* active */> | 
 | 708 |     computeStateFromNormalizedTime(S x) const { | 
 | 709 |         bool active = true; | 
 | 710 |         // handle reversal of position | 
 | 711 |         if (getFlags() & VolumeShaper::Operation::FLAG_REVERSE) { | 
 | 712 |             x = MAX_CURVE_TIME - x; | 
 | 713 |             VS_LOG("reversing to %f", x); | 
 | 714 |             if (x < MIN_CURVE_TIME) { | 
 | 715 |                 x = MIN_CURVE_TIME; | 
 | 716 |                 active = false; // at the end | 
 | 717 |             } else if (x > MAX_CURVE_TIME) { | 
 | 718 |                 x = MAX_CURVE_TIME; //early | 
 | 719 |             } | 
 | 720 |         } else { | 
 | 721 |             if (x < MIN_CURVE_TIME) { | 
 | 722 |                 x = MIN_CURVE_TIME; // early | 
 | 723 |             } else if (x > MAX_CURVE_TIME) { | 
 | 724 |                 x = MAX_CURVE_TIME; | 
 | 725 |                 active = false; // at end | 
 | 726 |             } | 
 | 727 |         } | 
 | 728 |         const S xOffset = x; | 
 | 729 |         const T volume = computeVolumeFromXOffset(xOffset); | 
 | 730 |         return std::make_tuple(volume, xOffset, active); | 
 | 731 |     } | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 732 | }; // VolumeShaper | 
 | 733 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 734 | /* VolumeHandler combines the volume factors of multiple VolumeShapers associated | 
 | 735 |  * with a player.  It is thread safe by synchronizing all public methods. | 
 | 736 |  * | 
 | 737 |  * This is a native-only implementation. | 
 | 738 |  * | 
 | 739 |  * The server side VolumeHandler is used to maintain a list of volume handlers, | 
 | 740 |  * keep state, and obtain volume. | 
 | 741 |  * | 
 | 742 |  * The client side VolumeHandler is used to maintain a list of volume handlers, | 
 | 743 |  * keep some partial state, and restore if the server dies. | 
 | 744 |  */ | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 745 | class VolumeHandler : public RefBase { | 
 | 746 | public: | 
 | 747 |     using S = float; | 
 | 748 |     using T = float; | 
 | 749 |  | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 750 |     // A volume handler which just keeps track of active VolumeShapers does not need sampleRate. | 
 | 751 |     VolumeHandler() | 
 | 752 |         : VolumeHandler(0 /* sampleRate */) { | 
 | 753 |     } | 
 | 754 |  | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 755 |     explicit VolumeHandler(uint32_t sampleRate) | 
 | 756 |         : mSampleRate((double)sampleRate) | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 757 |         , mLastFrame(0) | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 758 |         , mVolumeShaperIdCounter(VolumeShaper::kSystemVolumeShapersMax) | 
| Andy Hung | da540db | 2017-04-20 14:06:17 -0700 | [diff] [blame] | 759 |         , mLastVolume(1.f, false) { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 760 |     } | 
 | 761 |  | 
 | 762 |     VolumeShaper::Status applyVolumeShaper( | 
 | 763 |             const sp<VolumeShaper::Configuration> &configuration, | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 764 |             const sp<VolumeShaper::Operation> &operation_in) { | 
 | 765 |         // make a local copy of operation, as we modify it. | 
 | 766 |         sp<VolumeShaper::Operation> operation(new VolumeShaper::Operation(operation_in)); | 
| Andy Hung | 10cbff1 | 2017-02-21 17:30:14 -0800 | [diff] [blame] | 767 |         VS_LOG("applyVolumeShaper:configuration: %s", configuration->toString().c_str()); | 
 | 768 |         VS_LOG("applyVolumeShaper:operation: %s", operation->toString().c_str()); | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 769 |         AutoMutex _l(mLock); | 
 | 770 |         if (configuration == nullptr) { | 
 | 771 |             ALOGE("null configuration"); | 
 | 772 |             return VolumeShaper::Status(BAD_VALUE); | 
 | 773 |         } | 
 | 774 |         if (operation == nullptr) { | 
 | 775 |             ALOGE("null operation"); | 
 | 776 |             return VolumeShaper::Status(BAD_VALUE); | 
 | 777 |         } | 
 | 778 |         const int32_t id = configuration->getId(); | 
 | 779 |         if (id < 0) { | 
 | 780 |             ALOGE("negative id: %d", id); | 
 | 781 |             return VolumeShaper::Status(BAD_VALUE); | 
 | 782 |         } | 
 | 783 |         VS_LOG("applyVolumeShaper id: %d", id); | 
 | 784 |  | 
 | 785 |         switch (configuration->getType()) { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 786 |         case VolumeShaper::Configuration::TYPE_SCALE: { | 
 | 787 |             const int replaceId = operation->getReplaceId(); | 
 | 788 |             if (replaceId >= 0) { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 789 |                 VS_LOG("replacing %d", replaceId); | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 790 |                 auto replaceIt = findId_l(replaceId); | 
 | 791 |                 if (replaceIt == mVolumeShapers.end()) { | 
 | 792 |                     ALOGW("cannot find replace id: %d", replaceId); | 
 | 793 |                 } else { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 794 |                     if ((operation->getFlags() & VolumeShaper::Operation::FLAG_JOIN) != 0) { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 795 |                         // For join, we scale the start volume of the current configuration | 
 | 796 |                         // to match the last-used volume of the replacing VolumeShaper. | 
 | 797 |                         auto state = replaceIt->getState(); | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 798 |                         ALOGD("join: state:%s", state->toString().c_str()); | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 799 |                         if (state->getXOffset() >= 0) { // valid | 
 | 800 |                             const T volume = state->getVolume(); | 
 | 801 |                             ALOGD("join: scaling start volume to %f", volume); | 
 | 802 |                             configuration->scaleToStartVolume(volume); | 
 | 803 |                         } | 
 | 804 |                     } | 
 | 805 |                     (void)mVolumeShapers.erase(replaceIt); | 
 | 806 |                 } | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 807 |                 operation->setReplaceId(-1); | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 808 |             } | 
 | 809 |             // check if we have another of the same id. | 
 | 810 |             auto oldIt = findId_l(id); | 
 | 811 |             if (oldIt != mVolumeShapers.end()) { | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 812 |                 if ((operation->getFlags() | 
 | 813 |                         & VolumeShaper::Operation::FLAG_CREATE_IF_NECESSARY) != 0) { | 
 | 814 |                     // TODO: move the case to a separate function. | 
 | 815 |                     goto HANDLE_TYPE_ID; // no need to create, take over existing id. | 
 | 816 |                 } | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 817 |                 ALOGW("duplicate id, removing old %d", id); | 
 | 818 |                 (void)mVolumeShapers.erase(oldIt); | 
 | 819 |             } | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 820 |  | 
 | 821 |             /* Check if too many application VolumeShapers (with id >= kSystemVolumeShapersMax). | 
 | 822 |              * We check on the server side to ensure synchronization and robustness. | 
 | 823 |              * | 
 | 824 |              * This shouldn't fail on a replace command unless the replaced id is | 
 | 825 |              * already invalid (which *should* be checked in the Java layer). | 
 | 826 |              */ | 
 | 827 |             if (id >= VolumeShaper::kSystemVolumeShapersMax | 
 | 828 |                     && numberOfUserVolumeShapers_l() >= VolumeShaper::kUserVolumeShapersMax) { | 
 | 829 |                 ALOGW("Too many app VolumeShapers, cannot add to VolumeHandler"); | 
 | 830 |                 return VolumeShaper::Status(INVALID_OPERATION); | 
 | 831 |             } | 
 | 832 |  | 
 | 833 |             // create new VolumeShaper with default behavior. | 
 | 834 |             mVolumeShapers.emplace_back(configuration, new VolumeShaper::Operation()); | 
 | 835 |             VS_LOG("after adding, number of volumeShapers:%zu", mVolumeShapers.size()); | 
| Andy Hung | 10cbff1 | 2017-02-21 17:30:14 -0800 | [diff] [blame] | 836 |         } | 
 | 837 |         // fall through to handle the operation | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 838 |         HANDLE_TYPE_ID: | 
| Andy Hung | 10cbff1 | 2017-02-21 17:30:14 -0800 | [diff] [blame] | 839 |         case VolumeShaper::Configuration::TYPE_ID: { | 
 | 840 |             VS_LOG("trying to find id: %d", id); | 
 | 841 |             auto it = findId_l(id); | 
 | 842 |             if (it == mVolumeShapers.end()) { | 
 | 843 |                 VS_LOG("couldn't find id: %d", id); | 
 | 844 |                 return VolumeShaper::Status(INVALID_OPERATION); | 
 | 845 |             } | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 846 |             if ((operation->getFlags() & VolumeShaper::Operation::FLAG_TERMINATE) != 0) { | 
| Andy Hung | 10cbff1 | 2017-02-21 17:30:14 -0800 | [diff] [blame] | 847 |                 VS_LOG("terminate id: %d", id); | 
 | 848 |                 mVolumeShapers.erase(it); | 
 | 849 |                 break; | 
 | 850 |             } | 
 | 851 |             const bool clockTime = (it->mConfiguration->getOptionFlags() | 
 | 852 |                     & VolumeShaper::Configuration::OPTION_FLAG_CLOCK_TIME) != 0; | 
 | 853 |             if ((it->getFlags() & VolumeShaper::Operation::FLAG_REVERSE) != | 
 | 854 |                     (operation->getFlags() & VolumeShaper::Operation::FLAG_REVERSE)) { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 855 |                 if (it->isStarted()) { | 
 | 856 |                     const int64_t frameCount = clockTime ? VolumeShaper::getNowUs() : mLastFrame; | 
 | 857 |                     const S x = it->mXTranslate((T)frameCount); | 
 | 858 |                     VS_LOG("reverse normalizedTime: %f", x); | 
 | 859 |                     // reflect position | 
 | 860 |                     S target = MAX_CURVE_TIME - x; | 
 | 861 |                     if (target < MIN_CURVE_TIME) { | 
 | 862 |                         VS_LOG("clamp to start - begin immediately"); | 
 | 863 |                         target = MIN_CURVE_TIME; | 
 | 864 |                     } | 
 | 865 |                     VS_LOG("reverse normalizedTime target: %f", target); | 
 | 866 |                     it->mXTranslate.setOffset(it->mXTranslate.getOffset() | 
 | 867 |                             + (x - target) / it->mXTranslate.getScale()); | 
| Andy Hung | 10cbff1 | 2017-02-21 17:30:14 -0800 | [diff] [blame] | 868 |                 } | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 869 |                 // if not started, the delay offset doesn't change. | 
| Andy Hung | 10cbff1 | 2017-02-21 17:30:14 -0800 | [diff] [blame] | 870 |             } | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 871 |             const S xOffset = operation->getXOffset(); | 
 | 872 |             if (!std::isnan(xOffset)) { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 873 |                 if (it->isStarted()) { | 
 | 874 |                     const int64_t frameCount = clockTime ? VolumeShaper::getNowUs() : mLastFrame; | 
 | 875 |                     const S x = it->mXTranslate((T)frameCount); | 
 | 876 |                     VS_LOG("normalizedTime translation: %f", x); | 
 | 877 |                     const S target = | 
 | 878 |                             (operation->getFlags() & VolumeShaper::Operation::FLAG_REVERSE) != 0 ? | 
 | 879 |                                     MAX_CURVE_TIME - xOffset : xOffset; | 
 | 880 |                     VS_LOG("normalizedTime target x offset: %f", target); | 
 | 881 |                     it->mXTranslate.setOffset(it->mXTranslate.getOffset() | 
 | 882 |                             + (x - target) / it->mXTranslate.getScale()); | 
 | 883 |                 } else { | 
 | 884 |                     it->setDelayXOffset(xOffset); | 
 | 885 |                 } | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 886 |             } | 
| Andy Hung | 10cbff1 | 2017-02-21 17:30:14 -0800 | [diff] [blame] | 887 |             it->mOperation = operation; // replace the operation | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 888 |         } break; | 
 | 889 |         } | 
 | 890 |         return VolumeShaper::Status(id); | 
 | 891 |     } | 
 | 892 |  | 
 | 893 |     sp<VolumeShaper::State> getVolumeShaperState(int id) { | 
 | 894 |         AutoMutex _l(mLock); | 
 | 895 |         auto it = findId_l(id); | 
 | 896 |         if (it == mVolumeShapers.end()) { | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 897 |             VS_LOG("cannot find state for id: %d", id); | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 898 |             return nullptr; | 
 | 899 |         } | 
 | 900 |         return it->getState(); | 
 | 901 |     } | 
 | 902 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 903 |     /* getVolume() is not const, as it updates internal state. | 
 | 904 |      * Once called, any VolumeShapers not already started begin running. | 
 | 905 |      */ | 
| Andy Hung | 10cbff1 | 2017-02-21 17:30:14 -0800 | [diff] [blame] | 906 |     std::pair<T /* volume */, bool /* active */> getVolume(int64_t trackFrameCount) { | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 907 |         AutoMutex _l(mLock); | 
 | 908 |         mLastFrame = trackFrameCount; | 
 | 909 |         T volume(1); | 
| Andy Hung | 10cbff1 | 2017-02-21 17:30:14 -0800 | [diff] [blame] | 910 |         size_t activeCount = 0; | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 911 |         for (auto it = mVolumeShapers.begin(); it != mVolumeShapers.end();) { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 912 |             const std::pair<T, bool> shaperVolume = | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 913 |                     it->getVolume(trackFrameCount, mSampleRate); | 
 | 914 |             volume *= shaperVolume.first; | 
| Andy Hung | 10cbff1 | 2017-02-21 17:30:14 -0800 | [diff] [blame] | 915 |             activeCount += shaperVolume.second; | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 916 |             ++it; | 
 | 917 |         } | 
| Andy Hung | da540db | 2017-04-20 14:06:17 -0700 | [diff] [blame] | 918 |         mLastVolume = std::make_pair(volume, activeCount != 0); | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 919 |         VS_LOG("getVolume: <%f, %s>", mLastVolume.first, mLastVolume.second ? "true" : "false"); | 
| Andy Hung | da540db | 2017-04-20 14:06:17 -0700 | [diff] [blame] | 920 |         return mLastVolume; | 
 | 921 |     } | 
 | 922 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 923 |     /* Used by a client side VolumeHandler to ensure all the VolumeShapers | 
 | 924 |      * indicate that they have been started.  Upon a change in audioserver | 
 | 925 |      * output sink, this information is used for restoration of the server side | 
 | 926 |      * VolumeHandler. | 
 | 927 |      */ | 
| Andy Hung | 39399b6 | 2017-04-21 15:07:45 -0700 | [diff] [blame] | 928 |     void setStarted() { | 
 | 929 |         (void)getVolume(mLastFrame);  // getVolume() will start the individual VolumeShapers. | 
 | 930 |     } | 
 | 931 |  | 
| Andy Hung | da540db | 2017-04-20 14:06:17 -0700 | [diff] [blame] | 932 |     std::pair<T /* volume */, bool /* active */> getLastVolume() const { | 
 | 933 |         AutoMutex _l(mLock); | 
 | 934 |         return mLastVolume; | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 935 |     } | 
 | 936 |  | 
 | 937 |     std::string toString() const { | 
 | 938 |         AutoMutex _l(mLock); | 
 | 939 |         std::stringstream ss; | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 940 |         ss << "VolumeHandler{mSampleRate=" << mSampleRate; | 
 | 941 |         ss << ", mLastFrame=" << mLastFrame; | 
 | 942 |         ss << ", mVolumeShapers={"; | 
 | 943 |         bool first = true; | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 944 |         for (const auto &shaper : mVolumeShapers) { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 945 |             if (first) { | 
 | 946 |                 first = false; | 
 | 947 |             } else { | 
 | 948 |                 ss << ", "; | 
 | 949 |             } | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 950 |             ss << shaper.toString().c_str(); | 
 | 951 |         } | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 952 |         ss << "}}"; | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 953 |         return ss.str(); | 
 | 954 |     } | 
 | 955 |  | 
| Andy Hung | 39399b6 | 2017-04-21 15:07:45 -0700 | [diff] [blame] | 956 |     void forall(const std::function<VolumeShaper::Status (const VolumeShaper &)> &lambda) { | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 957 |         AutoMutex _l(mLock); | 
| Andy Hung | da540db | 2017-04-20 14:06:17 -0700 | [diff] [blame] | 958 |         VS_LOG("forall: mVolumeShapers.size() %zu", mVolumeShapers.size()); | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 959 |         for (const auto &shaper : mVolumeShapers) { | 
| Andy Hung | 39399b6 | 2017-04-21 15:07:45 -0700 | [diff] [blame] | 960 |             VolumeShaper::Status status = lambda(shaper); | 
 | 961 |             VS_LOG("forall applying lambda on shaper (%p): %d", &shaper, (int)status); | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 962 |         } | 
 | 963 |     } | 
 | 964 |  | 
 | 965 |     void reset() { | 
 | 966 |         AutoMutex _l(mLock); | 
 | 967 |         mVolumeShapers.clear(); | 
| Andy Hung | 7d712bb | 2017-04-20 14:23:41 -0700 | [diff] [blame] | 968 |         mLastFrame = 0; | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 969 |         // keep mVolumeShaperIdCounter as is. | 
 | 970 |     } | 
 | 971 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 972 |     /* Sets the configuration id if necessary - This is based on the counter | 
 | 973 |      * internal to the VolumeHandler. | 
 | 974 |      */ | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 975 |     void setIdIfNecessary(const sp<VolumeShaper::Configuration> &configuration) { | 
 | 976 |         if (configuration->getType() == VolumeShaper::Configuration::TYPE_SCALE) { | 
 | 977 |             const int id = configuration->getId(); | 
 | 978 |             if (id == -1) { | 
 | 979 |                 // Reassign to a unique id, skipping system ids. | 
 | 980 |                 AutoMutex _l(mLock); | 
 | 981 |                 while (true) { | 
 | 982 |                     if (mVolumeShaperIdCounter == INT32_MAX) { | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 983 |                         mVolumeShaperIdCounter = VolumeShaper::kSystemVolumeShapersMax; | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 984 |                     } else { | 
 | 985 |                         ++mVolumeShaperIdCounter; | 
 | 986 |                     } | 
 | 987 |                     if (findId_l(mVolumeShaperIdCounter) != mVolumeShapers.end()) { | 
 | 988 |                         continue; // collision with an existing id. | 
 | 989 |                     } | 
 | 990 |                     configuration->setId(mVolumeShaperIdCounter); | 
 | 991 |                     ALOGD("setting id to %d", mVolumeShaperIdCounter); | 
 | 992 |                     break; | 
 | 993 |                 } | 
 | 994 |             } | 
 | 995 |         } | 
 | 996 |     } | 
 | 997 |  | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 998 | private: | 
 | 999 |     std::list<VolumeShaper>::iterator findId_l(int32_t id) { | 
 | 1000 |         std::list<VolumeShaper>::iterator it = mVolumeShapers.begin(); | 
 | 1001 |         for (; it != mVolumeShapers.end(); ++it) { | 
 | 1002 |             if (it->mConfiguration->getId() == id) { | 
 | 1003 |                 break; | 
 | 1004 |             } | 
 | 1005 |         } | 
 | 1006 |         return it; | 
 | 1007 |     } | 
 | 1008 |  | 
| Andy Hung | f370264 | 2017-05-05 17:33:32 -0700 | [diff] [blame] | 1009 |     size_t numberOfUserVolumeShapers_l() const { | 
 | 1010 |         size_t count = 0; | 
 | 1011 |         for (const auto &shaper : mVolumeShapers) { | 
 | 1012 |             count += (shaper.mConfiguration->getId() >= VolumeShaper::kSystemVolumeShapersMax); | 
 | 1013 |         } | 
 | 1014 |         return count; | 
 | 1015 |     } | 
 | 1016 |  | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 1017 |     mutable Mutex mLock; | 
 | 1018 |     double mSampleRate; // in samples (frames) per second | 
| Andy Hung | 7d712bb | 2017-04-20 14:23:41 -0700 | [diff] [blame] | 1019 |     int64_t mLastFrame; // logging purpose only, 0 on start | 
| Andy Hung | 4ef88d7 | 2017-02-21 19:47:53 -0800 | [diff] [blame] | 1020 |     int32_t mVolumeShaperIdCounter; // a counter to return a unique volume shaper id. | 
| Andy Hung | da540db | 2017-04-20 14:06:17 -0700 | [diff] [blame] | 1021 |     std::pair<T /* volume */, bool /* active */> mLastVolume; | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 1022 |     std::list<VolumeShaper> mVolumeShapers; // list provides stable iterators on erase | 
 | 1023 | }; // VolumeHandler | 
 | 1024 |  | 
| Ivan Lozano | 8cf3a07 | 2017-08-09 09:01:33 -0700 | [diff] [blame] | 1025 | } // namespace media | 
 | 1026 |  | 
| Andy Hung | 9fc8b5c | 2017-01-24 13:36:48 -0800 | [diff] [blame] | 1027 | } // namespace android | 
 | 1028 |  | 
 | 1029 | #pragma pop_macro("LOG_TAG") | 
 | 1030 |  | 
 | 1031 | #endif // ANDROID_VOLUME_SHAPER_H |