AudioTrack: Fix timestamp jitter

When HAL out_get_presentation_position returns error,
use server position adjusted for latency.

Bug: 28250436
Change-Id: I3dbcb9b7c4e56d34e7e161d1a02d8f64afd602b9
diff --git a/include/media/AudioTimestamp.h b/include/media/AudioTimestamp.h
index 969003c..4f504a4 100644
--- a/include/media/AudioTimestamp.h
+++ b/include/media/AudioTimestamp.h
@@ -36,6 +36,7 @@
 
 struct ExtendedTimestamp {
     enum Location {
+        LOCATION_INVALID = -1,
         LOCATION_CLIENT,   // timestamp of last read frame from client-server track buffer
         LOCATION_SERVER,   // timestamp of newest frame from client-server track buffer
         LOCATION_KERNEL,   // timestamp of newest frame in the kernel (alsa) buffer.
@@ -89,8 +90,10 @@
     }
 
     // Returns the best timestamp as judged from the closest-to-hw stage in the
-    // pipeline with a valid timestamp.
-    status_t getBestTimestamp(int64_t *position, int64_t *time, int timebase) const {
+    // pipeline with a valid timestamp.  If the optional location parameter is non-null,
+    // it will be filled with the location where the time was obtained.
+    status_t getBestTimestamp(
+            int64_t *position, int64_t *time, int timebase, Location *location = nullptr) const {
         if (position == nullptr || time == nullptr
                 || timebase < 0 || timebase >= TIMEBASE_MAX) {
             return BAD_VALUE;
@@ -102,18 +105,21 @@
             if (mTimeNs[i] > 0) {
                 *position = mPosition[i];
                 *time = mTimeNs[i] + mTimebaseOffset[timebase];
+                if (location != nullptr) {
+                    *location = (Location)i;
+                }
                 return OK;
             }
         }
         return INVALID_OPERATION;
     }
 
-    status_t getBestTimestamp(AudioTimestamp *timestamp) const {
+    status_t getBestTimestamp(AudioTimestamp *timestamp, Location *location = nullptr) const {
         if (timestamp == nullptr) {
             return BAD_VALUE;
         }
         int64_t position, time;
-        if (getBestTimestamp(&position, &time, TIMEBASE_MONOTONIC) == OK) {
+        if (getBestTimestamp(&position, &time, TIMEBASE_MONOTONIC, location) == OK) {
             timestamp->mPosition = position;
             timestamp->mTime.tv_sec = time / 1000000000;
             timestamp->mTime.tv_nsec = time - timestamp->mTime.tv_sec * 1000000000LL;
diff --git a/include/media/AudioTrack.h b/include/media/AudioTrack.h
index fc7217a..3cadf6b 100644
--- a/include/media/AudioTrack.h
+++ b/include/media/AudioTrack.h
@@ -1024,6 +1024,8 @@
     bool                    mTimestampStartupGlitchReported; // reduce log spam
     bool                    mRetrogradeMotionReported; // reduce log spam
     AudioTimestamp          mPreviousTimestamp;     // used to detect retrograde motion
+    ExtendedTimestamp::Location mPreviousLocation;  // location used for previous timestamp
+    double                  mComputedLatencyMs;     // latency between server and kernel
 
     uint32_t                mUnderrunCountOffset;   // updated when restoring tracks
 
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index b9138f2..9821831 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -504,6 +504,8 @@
     mPreviousTimestampValid = false;
     mTimestampStartupGlitchReported = false;
     mRetrogradeMotionReported = false;
+    mPreviousLocation = ExtendedTimestamp::LOCATION_INVALID;
+    mComputedLatencyMs = 0.;
     mUnderrunCountOffset = 0;
     mFramesWritten = 0;
     mFramesWrittenServerOffset = 0;
@@ -536,6 +538,8 @@
         mPreviousTimestampValid = false;
         mTimestampStartupGlitchReported = false;
         mRetrogradeMotionReported = false;
+        mPreviousLocation = ExtendedTimestamp::LOCATION_INVALID;
+        mComputedLatencyMs = 0.;
 
         // read last server side position change via timestamp.
         ExtendedTimestamp ets;
@@ -2303,7 +2307,40 @@
         ExtendedTimestamp ets;
         status = mProxy->getTimestamp(&ets);
         if (status == OK) {
-            status = ets.getBestTimestamp(&timestamp);
+            ExtendedTimestamp::Location location;
+            status = ets.getBestTimestamp(&timestamp, &location);
+
+            if (status == OK) {
+                // It is possible that the best location has moved from the kernel to the server.
+                // In this case we adjust the position from the previous computed latency.
+                if (location == ExtendedTimestamp::LOCATION_SERVER) {
+                    ALOGW_IF(mPreviousLocation == ExtendedTimestamp::LOCATION_KERNEL,
+                            "getTimestamp() location moved from kernel to server");
+                    const double latencyMs = mComputedLatencyMs > 0.
+                            ? mComputedLatencyMs : mAfLatency;
+                    const int64_t frames =
+                            int64_t(latencyMs * mSampleRate * mPlaybackRate.mSpeed / 1000);
+                    ALOGV("mComputedLatencyMs:%lf  mAfLatency:%u  frame adjustment:%lld",
+                            mComputedLatencyMs, mAfLatency, (long long)frames);
+                    if (frames >= ets.mPosition[location]) {
+                        timestamp.mPosition = 0;
+                    } else {
+                        timestamp.mPosition = (uint32_t)(ets.mPosition[location] - frames);
+                    }
+                } else if (location == ExtendedTimestamp::LOCATION_KERNEL) {
+                    const double bufferDiffMs =
+                            (double)(ets.mPosition[ExtendedTimestamp::LOCATION_SERVER]
+                                   - ets.mPosition[ExtendedTimestamp::LOCATION_KERNEL])
+                                   * 1000 / ((double)mSampleRate * mPlaybackRate.mSpeed);
+                    mComputedLatencyMs = bufferDiffMs > 0. ? bufferDiffMs : 0.;
+                    ALOGV("mComputedLatencyMs:%lf  mAfLatency:%d",
+                            mComputedLatencyMs, mAfLatency);
+                }
+                mPreviousLocation = location;
+            } else {
+                // right after AudioTrack is started, one may not find a timestamp
+                ALOGV("getBestTimestamp did not find timestamp");
+            }
         }
         if (status == INVALID_OPERATION) {
             status = WOULD_BLOCK;