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
22namespace android {
23
24/*
25A general purpose lookup utility that defines a mapping between X and Y as a
26continuous set of line segments with shared (x, y) end-points.
27The (x, y) points must be added in order, monotonically increasing in both x and y;
28a log warning is emitted if this does not happen (See general usage notes below).
29
30A limited history of (x, y) points is kept for space reasons (See general usage notes).
31
32In AudioFlinger, we use the LinearMap to associate track frames to
33sink frames.  When we want to obtain a client track timestamp, we first
34get a timestamp from the sink.  The sink timestamp's position (mPosition)
35corresponds to the sink frames written. We use LinearMap to figure out which track frame
36the sink frame corresponds to. This allows us to substitute a track frame for the
37the sink frame (keeping the mTime identical) and return that timestamp back to the client.
38
39The method findX() can be used to retrieve an x value from a given y value and is
40used for timestamps, similarly for findY() which is provided for completeness.
41
42We update the (track frame, sink frame) points in the LinearMap each time we write data
43to the sink by the AudioFlinger PlaybackThread (MixerThread).
44
45
46AudioFlinger Timestamp Notes:
47
481) Example: Obtaining a track timestamp during playback.  In this case, the LinearMap
49looks something like this:
50
51Track Frame    Sink Frame
52(track start)
530              50000  (track starts here, the sink may already be running)
541000           51000
552000           52000
56
57When we request a track timestamp, we call the sink getTimestamp() and get for example
58mPosition = 51020.  Using the LinearMap, we find we have played to track frame 1020.
59We substitute the sink mPosition of 51020 with the track position 1020,
60and return that timestamp to the app.
61
622) Example: Obtaining a track timestamp duing pause. In this case, the LinearMap
63looks something like this:
64
65Track Frame    Sink Frame
66... (some time has gone by)
6715000          30000
6816000          31000
6917000          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).
7517000          33000
7617000          34000
77...
78
793) If the track underruns, it appears as if a pause was called on that track.
80
814) If there is an underrun in the HAL layer, then it may be possible that
82the 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
84HAL implementations of the sink getTimestamp. In that case, timing is lost
85and we will return the most recent track frame written.
86
875) When called with no points in the map, findX() returns the start value (default 0).
88This is consistent with starting after a stop() or flush().
89
906) Resuming after Track standby will be similar to coming out of pause, as the HAL ensures
91framesWritten() and getTimestamp() are contiguous for non-offloaded/direct tracks.
92
937) LinearMap works for different speeds and sample rates as it uses
94linear interpolation. Since AudioFlinger only updates speed and sample rate
95exactly at the sample points pushed into the LinearMap, the returned values
96from findX() and findY() are accurate regardless of how many speed or sample
97rate changes are made, so long as the coordinate looked up is within the
98sample history.
99
100General usage notes:
101
1021) In order for the LinearMap to work reliably, you cannot look backwards more
103than the size of its circular buffer history, set upon creation (typically 16).
104If you look back further, the position is extrapolated either from a passed in
105extrapolation parameter or from the oldest line segment.
106
1072) Points must monotonically increase in x and y. The increment between adjacent
108points cannot be greater than signed 32 bits. Wrap in the x, y coordinates are supported,
109since we use differences in our computation.
110
1113) If the frame data is discontinuous (due to stop or flush) call reset() to clear
112the sample counter.
113
1144) If (x, y) are not strictly monotonic increasing, i.e. (x2 > x1) and (y2 > y1),
115then one or both of the inverses y = f(x) or x = g(y) may have multiple solutions.
116In that case, the most recent solution is returned by findX() or findY().  We
117do not warn if (x2 == x1) or (y2 == y1), but we do logcat warn if (x2 < x1) or
118(y2 < y1).
119
1205) Due to rounding it is possible x != findX(findY(x)) or y != findY(findX(y))
121even when the inverse exists. Nevertheless, the values should be close.
122
123*/
124
125template <typename T>
126class LinearMap {
127public:
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
137    explicit LinearMap(size_t size)
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
237protected:
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
353private:
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
367