Andy Hung | e10393e | 2015-06-12 13:59:33 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2015 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_LINEAR_MAP_H |
| 18 | #define ANDROID_LINEAR_MAP_H |
| 19 | |
| 20 | #include <stdint.h> |
| 21 | |
| 22 | namespace android { |
| 23 | |
| 24 | /* |
| 25 | A general purpose lookup utility that defines a mapping between X and Y as a |
| 26 | continuous set of line segments with shared (x, y) end-points. |
| 27 | The (x, y) points must be added in order, monotonically increasing in both x and y; |
| 28 | a log warning is emitted if this does not happen (See general usage notes below). |
| 29 | |
| 30 | A limited history of (x, y) points is kept for space reasons (See general usage notes). |
| 31 | |
| 32 | In AudioFlinger, we use the LinearMap to associate track frames to |
| 33 | sink frames. When we want to obtain a client track timestamp, we first |
| 34 | get a timestamp from the sink. The sink timestamp's position (mPosition) |
| 35 | corresponds to the sink frames written. We use LinearMap to figure out which track frame |
| 36 | the sink frame corresponds to. This allows us to substitute a track frame for the |
| 37 | the sink frame (keeping the mTime identical) and return that timestamp back to the client. |
| 38 | |
| 39 | The method findX() can be used to retrieve an x value from a given y value and is |
| 40 | used for timestamps, similarly for findY() which is provided for completeness. |
| 41 | |
| 42 | We update the (track frame, sink frame) points in the LinearMap each time we write data |
| 43 | to the sink by the AudioFlinger PlaybackThread (MixerThread). |
| 44 | |
| 45 | |
| 46 | AudioFlinger Timestamp Notes: |
| 47 | |
| 48 | 1) Example: Obtaining a track timestamp during playback. In this case, the LinearMap |
| 49 | looks something like this: |
| 50 | |
| 51 | Track Frame Sink Frame |
| 52 | (track start) |
| 53 | 0 50000 (track starts here, the sink may already be running) |
| 54 | 1000 51000 |
| 55 | 2000 52000 |
| 56 | |
| 57 | When we request a track timestamp, we call the sink getTimestamp() and get for example |
| 58 | mPosition = 51020. Using the LinearMap, we find we have played to track frame 1020. |
| 59 | We substitute the sink mPosition of 51020 with the track position 1020, |
| 60 | and return that timestamp to the app. |
| 61 | |
| 62 | 2) Example: Obtaining a track timestamp duing pause. In this case, the LinearMap |
| 63 | looks something like this: |
| 64 | |
| 65 | Track Frame Sink Frame |
| 66 | ... (some time has gone by) |
| 67 | 15000 30000 |
| 68 | 16000 31000 |
| 69 | 17000 32000 |
| 70 | (pause here) |
| 71 | (suppose we call sink getTimestamp() here and get sink mPosition = 31100; that means |
| 72 | we have played to track frame 16100. The track timestamp mPosition will |
| 73 | continue to advance until the sink timestamp returns a value of mPosition |
| 74 | greater than 32000, corresponding to track frame 17000 when the pause was called). |
| 75 | 17000 33000 |
| 76 | 17000 34000 |
| 77 | ... |
| 78 | |
| 79 | 3) If the track underruns, it appears as if a pause was called on that track. |
| 80 | |
| 81 | 4) If there is an underrun in the HAL layer, then it may be possible that |
| 82 | the sink getTimestamp() will return a value greater than the number of frames written |
| 83 | (it should always be less). This should be rare, if not impossible by some |
| 84 | HAL implementations of the sink getTimestamp. In that case, timing is lost |
| 85 | and we will return the most recent track frame written. |
| 86 | |
| 87 | 5) When called with no points in the map, findX() returns the start value (default 0). |
| 88 | This is consistent with starting after a stop() or flush(). |
| 89 | |
| 90 | 6) Resuming after Track standby will be similar to coming out of pause, as the HAL ensures |
| 91 | framesWritten() and getTimestamp() are contiguous for non-offloaded/direct tracks. |
| 92 | |
| 93 | 7) LinearMap works for different speeds and sample rates as it uses |
| 94 | linear interpolation. Since AudioFlinger only updates speed and sample rate |
| 95 | exactly at the sample points pushed into the LinearMap, the returned values |
| 96 | from findX() and findY() are accurate regardless of how many speed or sample |
| 97 | rate changes are made, so long as the coordinate looked up is within the |
| 98 | sample history. |
| 99 | |
| 100 | General usage notes: |
| 101 | |
| 102 | 1) In order for the LinearMap to work reliably, you cannot look backwards more |
| 103 | than the size of its circular buffer history, set upon creation (typically 16). |
| 104 | If you look back further, the position is extrapolated either from a passed in |
| 105 | extrapolation parameter or from the oldest line segment. |
| 106 | |
| 107 | 2) Points must monotonically increase in x and y. The increment between adjacent |
| 108 | points cannot be greater than signed 32 bits. Wrap in the x, y coordinates are supported, |
| 109 | since we use differences in our computation. |
| 110 | |
| 111 | 3) If the frame data is discontinuous (due to stop or flush) call reset() to clear |
| 112 | the sample counter. |
| 113 | |
| 114 | 4) If (x, y) are not strictly monotonic increasing, i.e. (x2 > x1) and (y2 > y1), |
| 115 | then one or both of the inverses y = f(x) or x = g(y) may have multiple solutions. |
| 116 | In that case, the most recent solution is returned by findX() or findY(). We |
| 117 | do not warn if (x2 == x1) or (y2 == y1), but we do logcat warn if (x2 < x1) or |
| 118 | (y2 < y1). |
| 119 | |
| 120 | 5) Due to rounding it is possible x != findX(findY(x)) or y != findY(findX(y)) |
| 121 | even when the inverse exists. Nevertheless, the values should be close. |
| 122 | |
| 123 | */ |
| 124 | |
| 125 | template <typename T> |
| 126 | class LinearMap { |
| 127 | public: |
| 128 | // This enumeration describes the reliability of the findX() or findY() estimation |
| 129 | // in descending order. |
| 130 | enum FindMethod { |
| 131 | FIND_METHOD_INTERPOLATION, // High reliability (errors due to rounding) |
| 132 | FIND_METHOD_FORWARD_EXTRAPOLATION, // Reliability based on no future speed changes |
| 133 | FIND_METHOD_BACKWARD_EXTRAPOLATION, // Reliability based on prior estimated speed |
| 134 | FIND_METHOD_START_VALUE, // No samples in history, using start value |
| 135 | }; |
| 136 | |
Chih-Hung Hsieh | 34f28cb | 2016-08-29 14:18:27 -0700 | [diff] [blame] | 137 | explicit LinearMap(size_t size) |
Andy Hung | e10393e | 2015-06-12 13:59:33 -0700 | [diff] [blame] | 138 | : mSize(size), |
| 139 | mPos(0), // a circular buffer, so could start anywhere. the first sample is at 1. |
| 140 | mSamples(0), |
| 141 | // mStepValid(false), // only valid if mSamples > 1 |
| 142 | // mExtrapolateTail(false), // only valid if mSamples > 0 |
| 143 | mX(new T[size]), |
| 144 | mY(new T[size]) { } |
| 145 | |
| 146 | ~LinearMap() { |
| 147 | delete[] mX; |
| 148 | delete[] mY; |
| 149 | } |
| 150 | |
| 151 | // Add a new sample point to the linear map. |
| 152 | // |
| 153 | // The difference between the new sample and the previous sample |
| 154 | // in the x or y coordinate must be less than INT32_MAX for purposes |
| 155 | // of the linear interpolation or extrapolation. |
| 156 | // |
| 157 | // The value should be monotonic increasing (e.g. diff >= 0); |
| 158 | // logcat warnings are issued if they are not. |
| 159 | __attribute__((no_sanitize("integer"))) |
| 160 | void push(T x, T y) { |
| 161 | // Assumption: we assume x, y are monotonic increasing values, |
| 162 | // which (can) wrap in precision no less than 32 bits and have |
| 163 | // "step" or differences between adjacent points less than 32 bits. |
| 164 | |
| 165 | if (mSamples > 0) { |
| 166 | const bool lastStepValid = mStepValid; |
| 167 | int32_t xdiff; |
| 168 | int32_t ydiff; |
| 169 | // check difference assumption here |
| 170 | mStepValid = checkedDiff(&xdiff, x, mX[mPos], "x") |
| 171 | & /* bitwise AND to always warn for ydiff, though logical AND is also OK */ |
| 172 | checkedDiff(&ydiff, y, mY[mPos], "y"); |
| 173 | |
| 174 | // Optimization: do not add a new sample if the line segment would |
| 175 | // simply extend the previous line segment. This extends the useful |
| 176 | // history by removing redundant points. |
| 177 | if (mSamples > 1 && mStepValid && lastStepValid) { |
| 178 | const size_t prev = previousPosition(); |
| 179 | const int32_t xdiff2 = x - mX[prev]; |
| 180 | const int32_t ydiff2 = y - mY[prev]; |
| 181 | |
| 182 | // if both current step and previous step are valid (non-negative and |
| 183 | // less than INT32_MAX for precision greater than 4 bytes) |
| 184 | // then the sum of the two steps is valid when the |
| 185 | // int32_t difference is non-negative. |
| 186 | if (xdiff2 >= 0 && ydiff2 >= 0 |
| 187 | && (int64_t)xdiff2 * ydiff == (int64_t)ydiff2 * xdiff) { |
| 188 | // ALOGD("reusing sample! (%u, %u) sample depth %zd", x, y, mSamples); |
| 189 | mX[mPos] = x; |
| 190 | mY[mPos] = y; |
| 191 | return; |
| 192 | } |
| 193 | } |
| 194 | } |
| 195 | if (++mPos >= mSize) { |
| 196 | mPos = 0; |
| 197 | } |
| 198 | if (mSamples < mSize) { |
| 199 | mExtrapolateTail = false; |
| 200 | ++mSamples; |
| 201 | } else { |
| 202 | // we enable extrapolation beyond the oldest sample |
| 203 | // if the sample buffers are completely full and we |
| 204 | // no longer know the full history. |
| 205 | mExtrapolateTail = true; |
| 206 | } |
| 207 | mX[mPos] = x; |
| 208 | mY[mPos] = y; |
| 209 | } |
| 210 | |
| 211 | // clear all samples from the circular array |
| 212 | void reset() { |
| 213 | // no need to reset mPos, we use a circular buffer. |
| 214 | // computed values such as mStepValid are set after a subsequent push(). |
| 215 | mSamples = 0; |
| 216 | } |
| 217 | |
| 218 | // returns true if LinearMap contains at least one sample. |
| 219 | bool hasData() const { |
| 220 | return mSamples != 0; |
| 221 | } |
| 222 | |
| 223 | // find the corresponding X point from a Y point. |
| 224 | // See findU for details. |
| 225 | __attribute__((no_sanitize("integer"))) |
| 226 | T findX(T y, FindMethod *method = NULL, double extrapolation = 0.0, T startValue = 0) const { |
| 227 | return findU(y, mX, mY, method, extrapolation, startValue); |
| 228 | } |
| 229 | |
| 230 | // find the corresponding Y point from a X point. |
| 231 | // See findU for details. |
| 232 | __attribute__((no_sanitize("integer"))) |
| 233 | T findY(T x, FindMethod *method = NULL, double extrapolation = 0.0, T startValue = 0) const { |
| 234 | return findU(x, mY, mX, method, extrapolation, startValue); |
| 235 | } |
| 236 | |
| 237 | protected: |
| 238 | |
| 239 | // returns false if the diff is out of int32_t bounds or negative. |
| 240 | __attribute__((no_sanitize("integer"))) |
| 241 | static inline bool checkedDiff(int32_t *diff, T x2, T x1, const char *coord) { |
| 242 | if (sizeof(T) >= 8) { |
| 243 | const int64_t diff64 = x2 - x1; |
| 244 | *diff = (int32_t)diff64; // intentionally lose precision |
| 245 | if (diff64 > INT32_MAX) { |
| 246 | ALOGW("LinearMap: %s overflow diff(%lld) from %llu - %llu exceeds INT32_MAX", |
| 247 | coord, (long long)diff64, |
| 248 | (unsigned long long)x2, (unsigned long long)x1); |
| 249 | return false; |
| 250 | } else if (diff64 < 0) { |
| 251 | ALOGW("LinearMap: %s negative diff(%lld) from %llu - %llu", |
| 252 | coord, (long long)diff64, |
| 253 | (unsigned long long)x2, (unsigned long long)x1); |
| 254 | return false; |
| 255 | } |
| 256 | return true; |
| 257 | } |
| 258 | // for 32 bit integers we cannot detect overflow (it |
| 259 | // shows up as a negative difference). |
| 260 | *diff = x2 - x1; |
| 261 | if (*diff < 0) { |
| 262 | ALOGW("LinearMap: %s negative diff(%d) from %u - %u", |
| 263 | coord, *diff, (unsigned)x2, (unsigned)x1); |
| 264 | return false; |
| 265 | } |
| 266 | return true; |
| 267 | } |
| 268 | |
| 269 | // Returns the previous position in the mSamples array |
| 270 | // going backwards back steps. |
| 271 | // |
| 272 | // Parameters: |
| 273 | // back: number of backward steps, cannot be less than zero or greater than mSamples. |
| 274 | // |
| 275 | __attribute__((no_sanitize("integer"))) |
| 276 | size_t previousPosition(ssize_t back = 1) const { |
| 277 | LOG_ALWAYS_FATAL_IF(back < 0 || (size_t)back > mSamples, "Invalid back(%zd)", back); |
| 278 | ssize_t position = mPos - back; |
| 279 | if (position < 0) position += mSize; |
| 280 | return (size_t)position; |
| 281 | } |
| 282 | |
| 283 | // A generic implementation of finding the "other coordinate" with coordinates |
| 284 | // (u, v) = (x, y) or (u, v) = (y, x). |
| 285 | // |
| 286 | // Parameters: |
| 287 | // uArray: the u axis samples. |
| 288 | // vArray: the v axis samples. |
| 289 | // method: [out] how the returned value was computed. |
| 290 | // extrapolation: the slope used when extrapolating from the |
| 291 | // first sample value or the last sample value in the history. |
| 292 | // If mExtrapolateTail is set, the slope of the last line segment |
| 293 | // is used if the extrapolation parameter is zero to continue the tail of history. |
| 294 | // At this time, we do not use a different value for forward extrapolation from the |
| 295 | // head of history from backward extrapolation from the tail of history. |
| 296 | // TODO: back extrapolation value could be stored along with mX, mY in history. |
| 297 | // startValue: used only when there are no samples in history. One can detect |
| 298 | // whether there are samples in history by the method hasData(). |
| 299 | // |
| 300 | __attribute__((no_sanitize("integer"))) |
| 301 | T findU(T v, T *uArray, T *vArray, FindMethod *method, |
| 302 | double extrapolation, T startValue) const { |
| 303 | if (mSamples == 0) { |
| 304 | if (method != NULL) { |
| 305 | *method = FIND_METHOD_START_VALUE; |
| 306 | } |
| 307 | return startValue; // nothing yet |
| 308 | } |
| 309 | ssize_t previous = 0; |
| 310 | int32_t diff = 0; |
| 311 | for (ssize_t i = 0; i < (ssize_t)mSamples; ++i) { |
| 312 | size_t current = previousPosition(i); |
| 313 | |
| 314 | // Assumption: even though the type "T" may have precision greater |
| 315 | // than 32 bits, the difference between adjacent points is limited to 32 bits. |
| 316 | diff = v - vArray[current]; |
| 317 | if (diff >= 0 || |
| 318 | (i == (ssize_t)mSamples - 1 && mExtrapolateTail && extrapolation == 0.0)) { |
| 319 | // ALOGD("depth = %zd out of %zd", i, limit); |
| 320 | if (i == 0) { |
| 321 | if (method != NULL) { |
| 322 | *method = FIND_METHOD_FORWARD_EXTRAPOLATION; |
| 323 | } |
| 324 | return uArray[current] + diff * extrapolation; |
| 325 | } |
| 326 | // interpolate / extrapolate: For this computation, we |
| 327 | // must use differentials here otherwise we have inconsistent |
| 328 | // values on modulo wrap. previous is always valid here since |
| 329 | // i > 0. we also perform rounding with the assumption |
| 330 | // that uStep, vStep, and diff are non-negative. |
| 331 | int32_t uStep = uArray[previous] - uArray[current]; // non-negative |
| 332 | int32_t vStep = vArray[previous] - vArray[current]; // positive |
| 333 | T u = uStep <= 0 || vStep <= 0 ? // we do not permit negative ustep or vstep |
| 334 | uArray[current] |
| 335 | : ((int64_t)diff * uStep + (vStep >> 1)) / vStep + uArray[current]; |
| 336 | // ALOGD("u:%u diff:%d uStep:%d vStep:%d u_current:%d", |
| 337 | // u, diff, uStep, vStep, uArray[current]); |
| 338 | if (method != NULL) { |
| 339 | *method = (diff >= 0) ? |
| 340 | FIND_METHOD_INTERPOLATION : FIND_METHOD_BACKWARD_EXTRAPOLATION; |
| 341 | } |
| 342 | return u; |
| 343 | } |
| 344 | previous = current; |
| 345 | } |
| 346 | // previous is always valid here. |
| 347 | if (method != NULL) { |
| 348 | *method = FIND_METHOD_BACKWARD_EXTRAPOLATION; |
| 349 | } |
| 350 | return uArray[previous] + diff * extrapolation; |
| 351 | } |
| 352 | |
| 353 | private: |
| 354 | const size_t mSize; // Size of mX and mY arrays (history). |
| 355 | size_t mPos; // Index in mX and mY of last pushed data; |
| 356 | // (incremented after push) [0, mSize - 1]. |
| 357 | size_t mSamples; // Number of valid samples in the array [0, mSize]. |
| 358 | bool mStepValid; // Last sample step was valid (non-negative) |
| 359 | bool mExtrapolateTail; // extrapolate tail using oldest line segment |
| 360 | T * const mX; // History of X values as a circular array. |
| 361 | T * const mY; // History of Y values as a circular array. |
| 362 | }; |
| 363 | |
| 364 | } // namespace android |
| 365 | |
| 366 | #endif // ANDROID_LINEAR_MAP_H |