367 lines
16 KiB
C++
367 lines
16 KiB
C++
/*
|
|
* Copyright 2015 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#ifndef ANDROID_LINEAR_MAP_H
|
|
#define ANDROID_LINEAR_MAP_H
|
|
|
|
#include <stdint.h>
|
|
|
|
namespace android {
|
|
|
|
/*
|
|
A general purpose lookup utility that defines a mapping between X and Y as a
|
|
continuous set of line segments with shared (x, y) end-points.
|
|
The (x, y) points must be added in order, monotonically increasing in both x and y;
|
|
a log warning is emitted if this does not happen (See general usage notes below).
|
|
|
|
A limited history of (x, y) points is kept for space reasons (See general usage notes).
|
|
|
|
In AudioFlinger, we use the LinearMap to associate track frames to
|
|
sink frames. When we want to obtain a client track timestamp, we first
|
|
get a timestamp from the sink. The sink timestamp's position (mPosition)
|
|
corresponds to the sink frames written. We use LinearMap to figure out which track frame
|
|
the sink frame corresponds to. This allows us to substitute a track frame for the
|
|
the sink frame (keeping the mTime identical) and return that timestamp back to the client.
|
|
|
|
The method findX() can be used to retrieve an x value from a given y value and is
|
|
used for timestamps, similarly for findY() which is provided for completeness.
|
|
|
|
We update the (track frame, sink frame) points in the LinearMap each time we write data
|
|
to the sink by the AudioFlinger PlaybackThread (MixerThread).
|
|
|
|
|
|
AudioFlinger Timestamp Notes:
|
|
|
|
1) Example: Obtaining a track timestamp during playback. In this case, the LinearMap
|
|
looks something like this:
|
|
|
|
Track Frame Sink Frame
|
|
(track start)
|
|
0 50000 (track starts here, the sink may already be running)
|
|
1000 51000
|
|
2000 52000
|
|
|
|
When we request a track timestamp, we call the sink getTimestamp() and get for example
|
|
mPosition = 51020. Using the LinearMap, we find we have played to track frame 1020.
|
|
We substitute the sink mPosition of 51020 with the track position 1020,
|
|
and return that timestamp to the app.
|
|
|
|
2) Example: Obtaining a track timestamp duing pause. In this case, the LinearMap
|
|
looks something like this:
|
|
|
|
Track Frame Sink Frame
|
|
... (some time has gone by)
|
|
15000 30000
|
|
16000 31000
|
|
17000 32000
|
|
(pause here)
|
|
(suppose we call sink getTimestamp() here and get sink mPosition = 31100; that means
|
|
we have played to track frame 16100. The track timestamp mPosition will
|
|
continue to advance until the sink timestamp returns a value of mPosition
|
|
greater than 32000, corresponding to track frame 17000 when the pause was called).
|
|
17000 33000
|
|
17000 34000
|
|
...
|
|
|
|
3) If the track underruns, it appears as if a pause was called on that track.
|
|
|
|
4) If there is an underrun in the HAL layer, then it may be possible that
|
|
the sink getTimestamp() will return a value greater than the number of frames written
|
|
(it should always be less). This should be rare, if not impossible by some
|
|
HAL implementations of the sink getTimestamp. In that case, timing is lost
|
|
and we will return the most recent track frame written.
|
|
|
|
5) When called with no points in the map, findX() returns the start value (default 0).
|
|
This is consistent with starting after a stop() or flush().
|
|
|
|
6) Resuming after Track standby will be similar to coming out of pause, as the HAL ensures
|
|
framesWritten() and getTimestamp() are contiguous for non-offloaded/direct tracks.
|
|
|
|
7) LinearMap works for different speeds and sample rates as it uses
|
|
linear interpolation. Since AudioFlinger only updates speed and sample rate
|
|
exactly at the sample points pushed into the LinearMap, the returned values
|
|
from findX() and findY() are accurate regardless of how many speed or sample
|
|
rate changes are made, so long as the coordinate looked up is within the
|
|
sample history.
|
|
|
|
General usage notes:
|
|
|
|
1) In order for the LinearMap to work reliably, you cannot look backwards more
|
|
than the size of its circular buffer history, set upon creation (typically 16).
|
|
If you look back further, the position is extrapolated either from a passed in
|
|
extrapolation parameter or from the oldest line segment.
|
|
|
|
2) Points must monotonically increase in x and y. The increment between adjacent
|
|
points cannot be greater than signed 32 bits. Wrap in the x, y coordinates are supported,
|
|
since we use differences in our computation.
|
|
|
|
3) If the frame data is discontinuous (due to stop or flush) call reset() to clear
|
|
the sample counter.
|
|
|
|
4) If (x, y) are not strictly monotonic increasing, i.e. (x2 > x1) and (y2 > y1),
|
|
then one or both of the inverses y = f(x) or x = g(y) may have multiple solutions.
|
|
In that case, the most recent solution is returned by findX() or findY(). We
|
|
do not warn if (x2 == x1) or (y2 == y1), but we do logcat warn if (x2 < x1) or
|
|
(y2 < y1).
|
|
|
|
5) Due to rounding it is possible x != findX(findY(x)) or y != findY(findX(y))
|
|
even when the inverse exists. Nevertheless, the values should be close.
|
|
|
|
*/
|
|
|
|
template <typename T>
|
|
class LinearMap {
|
|
public:
|
|
// This enumeration describes the reliability of the findX() or findY() estimation
|
|
// in descending order.
|
|
enum FindMethod {
|
|
FIND_METHOD_INTERPOLATION, // High reliability (errors due to rounding)
|
|
FIND_METHOD_FORWARD_EXTRAPOLATION, // Reliability based on no future speed changes
|
|
FIND_METHOD_BACKWARD_EXTRAPOLATION, // Reliability based on prior estimated speed
|
|
FIND_METHOD_START_VALUE, // No samples in history, using start value
|
|
};
|
|
|
|
explicit LinearMap(size_t size)
|
|
: mSize(size),
|
|
mPos(0), // a circular buffer, so could start anywhere. the first sample is at 1.
|
|
mSamples(0),
|
|
// mStepValid(false), // only valid if mSamples > 1
|
|
// mExtrapolateTail(false), // only valid if mSamples > 0
|
|
mX(new T[size]),
|
|
mY(new T[size]) { }
|
|
|
|
~LinearMap() {
|
|
delete[] mX;
|
|
delete[] mY;
|
|
}
|
|
|
|
// Add a new sample point to the linear map.
|
|
//
|
|
// The difference between the new sample and the previous sample
|
|
// in the x or y coordinate must be less than INT32_MAX for purposes
|
|
// of the linear interpolation or extrapolation.
|
|
//
|
|
// The value should be monotonic increasing (e.g. diff >= 0);
|
|
// logcat warnings are issued if they are not.
|
|
__attribute__((no_sanitize("integer")))
|
|
void push(T x, T y) {
|
|
// Assumption: we assume x, y are monotonic increasing values,
|
|
// which (can) wrap in precision no less than 32 bits and have
|
|
// "step" or differences between adjacent points less than 32 bits.
|
|
|
|
if (mSamples > 0) {
|
|
const bool lastStepValid = mStepValid;
|
|
int32_t xdiff;
|
|
int32_t ydiff;
|
|
// check difference assumption here
|
|
bool checkXDiff = checkedDiff(&xdiff, x, mX[mPos], "x");
|
|
bool checkYDiff = checkedDiff(&ydiff, y, mY[mPos], "y");
|
|
mStepValid = checkXDiff && checkYDiff;
|
|
|
|
// Optimization: do not add a new sample if the line segment would
|
|
// simply extend the previous line segment. This extends the useful
|
|
// history by removing redundant points.
|
|
if (mSamples > 1 && mStepValid && lastStepValid) {
|
|
const size_t prev = previousPosition();
|
|
const int32_t xdiff2 = x - mX[prev];
|
|
const int32_t ydiff2 = y - mY[prev];
|
|
|
|
// if both current step and previous step are valid (non-negative and
|
|
// less than INT32_MAX for precision greater than 4 bytes)
|
|
// then the sum of the two steps is valid when the
|
|
// int32_t difference is non-negative.
|
|
if (xdiff2 >= 0 && ydiff2 >= 0
|
|
&& (int64_t)xdiff2 * ydiff == (int64_t)ydiff2 * xdiff) {
|
|
// ALOGD("reusing sample! (%u, %u) sample depth %zd", x, y, mSamples);
|
|
mX[mPos] = x;
|
|
mY[mPos] = y;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (++mPos >= mSize) {
|
|
mPos = 0;
|
|
}
|
|
if (mSamples < mSize) {
|
|
mExtrapolateTail = false;
|
|
++mSamples;
|
|
} else {
|
|
// we enable extrapolation beyond the oldest sample
|
|
// if the sample buffers are completely full and we
|
|
// no longer know the full history.
|
|
mExtrapolateTail = true;
|
|
}
|
|
mX[mPos] = x;
|
|
mY[mPos] = y;
|
|
}
|
|
|
|
// clear all samples from the circular array
|
|
void reset() {
|
|
// no need to reset mPos, we use a circular buffer.
|
|
// computed values such as mStepValid are set after a subsequent push().
|
|
mSamples = 0;
|
|
}
|
|
|
|
// returns true if LinearMap contains at least one sample.
|
|
bool hasData() const {
|
|
return mSamples != 0;
|
|
}
|
|
|
|
// find the corresponding X point from a Y point.
|
|
// See findU for details.
|
|
__attribute__((no_sanitize("integer")))
|
|
T findX(T y, FindMethod *method = NULL, double extrapolation = 0.0, T startValue = 0) const {
|
|
return findU(y, mX, mY, method, extrapolation, startValue);
|
|
}
|
|
|
|
// find the corresponding Y point from a X point.
|
|
// See findU for details.
|
|
__attribute__((no_sanitize("integer")))
|
|
T findY(T x, FindMethod *method = NULL, double extrapolation = 0.0, T startValue = 0) const {
|
|
return findU(x, mY, mX, method, extrapolation, startValue);
|
|
}
|
|
|
|
protected:
|
|
|
|
// returns false if the diff is out of int32_t bounds or negative.
|
|
__attribute__((no_sanitize("integer")))
|
|
static inline bool checkedDiff(int32_t *diff, T x2, T x1, const char *coord) {
|
|
if (sizeof(T) >= 8) {
|
|
const int64_t diff64 = x2 - x1;
|
|
*diff = (int32_t)diff64; // intentionally lose precision
|
|
if (diff64 > INT32_MAX) {
|
|
ALOGW("LinearMap: %s overflow diff(%lld) from %llu - %llu exceeds INT32_MAX",
|
|
coord, (long long)diff64,
|
|
(unsigned long long)x2, (unsigned long long)x1);
|
|
return false;
|
|
} else if (diff64 < 0) {
|
|
ALOGW("LinearMap: %s negative diff(%lld) from %llu - %llu",
|
|
coord, (long long)diff64,
|
|
(unsigned long long)x2, (unsigned long long)x1);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
// for 32 bit integers we cannot detect overflow (it
|
|
// shows up as a negative difference).
|
|
*diff = x2 - x1;
|
|
if (*diff < 0) {
|
|
ALOGW("LinearMap: %s negative diff(%d) from %u - %u",
|
|
coord, *diff, (unsigned)x2, (unsigned)x1);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Returns the previous position in the mSamples array
|
|
// going backwards back steps.
|
|
//
|
|
// Parameters:
|
|
// back: number of backward steps, cannot be less than zero or greater than mSamples.
|
|
//
|
|
__attribute__((no_sanitize("integer")))
|
|
size_t previousPosition(ssize_t back = 1) const {
|
|
LOG_ALWAYS_FATAL_IF(back < 0 || (size_t)back > mSamples, "Invalid back(%zd)", back);
|
|
ssize_t position = mPos - back;
|
|
if (position < 0) position += mSize;
|
|
return (size_t)position;
|
|
}
|
|
|
|
// A generic implementation of finding the "other coordinate" with coordinates
|
|
// (u, v) = (x, y) or (u, v) = (y, x).
|
|
//
|
|
// Parameters:
|
|
// uArray: the u axis samples.
|
|
// vArray: the v axis samples.
|
|
// method: [out] how the returned value was computed.
|
|
// extrapolation: the slope used when extrapolating from the
|
|
// first sample value or the last sample value in the history.
|
|
// If mExtrapolateTail is set, the slope of the last line segment
|
|
// is used if the extrapolation parameter is zero to continue the tail of history.
|
|
// At this time, we do not use a different value for forward extrapolation from the
|
|
// head of history from backward extrapolation from the tail of history.
|
|
// TODO: back extrapolation value could be stored along with mX, mY in history.
|
|
// startValue: used only when there are no samples in history. One can detect
|
|
// whether there are samples in history by the method hasData().
|
|
//
|
|
__attribute__((no_sanitize("integer")))
|
|
T findU(T v, T *uArray, T *vArray, FindMethod *method,
|
|
double extrapolation, T startValue) const {
|
|
if (mSamples == 0) {
|
|
if (method != NULL) {
|
|
*method = FIND_METHOD_START_VALUE;
|
|
}
|
|
return startValue; // nothing yet
|
|
}
|
|
ssize_t previous = 0;
|
|
int64_t diff = 0;
|
|
for (ssize_t i = 0; i < (ssize_t)mSamples; ++i) {
|
|
size_t current = previousPosition(i);
|
|
|
|
// Assumption: even though the type "T" may have precision greater
|
|
// than 32 bits, the difference between adjacent points is limited to 32 bits.
|
|
diff = v - vArray[current];
|
|
if (diff >= 0 ||
|
|
(i == (ssize_t)mSamples - 1 && mExtrapolateTail && extrapolation == 0.0)) {
|
|
// ALOGD("depth = %zd out of %zd", i, limit);
|
|
if (i == 0) {
|
|
if (method != NULL) {
|
|
*method = FIND_METHOD_FORWARD_EXTRAPOLATION;
|
|
}
|
|
return uArray[current] + diff * extrapolation;
|
|
}
|
|
// interpolate / extrapolate: For this computation, we
|
|
// must use differentials here otherwise we have inconsistent
|
|
// values on modulo wrap. previous is always valid here since
|
|
// i > 0. we also perform rounding with the assumption
|
|
// that uStep, vStep, and diff are non-negative.
|
|
int32_t uStep = uArray[previous] - uArray[current]; // non-negative
|
|
int32_t vStep = vArray[previous] - vArray[current]; // positive
|
|
T u = uStep <= 0 || vStep <= 0 ? // we do not permit negative ustep or vstep
|
|
uArray[current]
|
|
: ((int64_t)diff * uStep + (vStep >> 1)) / vStep + uArray[current];
|
|
// ALOGD("u:%u diff:%lld uStep:%d vStep:%d u_current:%d",
|
|
// u, diff, uStep, vStep, uArray[current]);
|
|
if (method != NULL) {
|
|
*method = (diff >= 0) ?
|
|
FIND_METHOD_INTERPOLATION : FIND_METHOD_BACKWARD_EXTRAPOLATION;
|
|
}
|
|
return u;
|
|
}
|
|
previous = current;
|
|
}
|
|
// previous is always valid here.
|
|
if (method != NULL) {
|
|
*method = FIND_METHOD_BACKWARD_EXTRAPOLATION;
|
|
}
|
|
return uArray[previous] + diff * extrapolation;
|
|
}
|
|
|
|
private:
|
|
const size_t mSize; // Size of mX and mY arrays (history).
|
|
size_t mPos; // Index in mX and mY of last pushed data;
|
|
// (incremented after push) [0, mSize - 1].
|
|
size_t mSamples; // Number of valid samples in the array [0, mSize].
|
|
bool mStepValid; // Last sample step was valid (non-negative)
|
|
bool mExtrapolateTail; // extrapolate tail using oldest line segment
|
|
T * const mX; // History of X values as a circular array.
|
|
T * const mY; // History of Y values as a circular array.
|
|
};
|
|
|
|
} // namespace android
|
|
|
|
#endif // ANDROID_LINEAR_MAP_H
|