mediaplayer: limit scheduling video frames into the future

This addresses when video timestamps jumps before an audio timestamp,
but still works on slideshow video clips (<=1fps).

This, however, will not skip time-changes on video-only live video
streams, as we cannot distinguish live slideshow video clips from
non-slideshow ones.

Bug: 18032127
Change-Id: I959a714edfe1c8cf3b84704c693dcd1b3e5b7855
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
index 46a2590..638d9bc 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
@@ -82,7 +82,9 @@
       mAudioRenderingStartGeneration(0),
       mAudioOffloadPauseTimeoutGeneration(0),
       mAudioOffloadTornDown(false),
-      mCurrentOffloadInfo(AUDIO_INFO_INITIALIZER) {
+      mCurrentOffloadInfo(AUDIO_INFO_INITIALIZER),
+      mTotalBuffersQueued(0),
+      mLastAudioBufferDrained(0) {
     readProperties();
 }
 
@@ -361,6 +363,19 @@
             break;
         }
 
+        case kWhatPostDrainVideoQueue:
+        {
+            int32_t generation;
+            CHECK(msg->findInt32("generation", &generation));
+            if (generation != mVideoQueueGeneration) {
+                break;
+            }
+
+            mDrainVideoQueuePending = false;
+            postDrainVideoQueue();
+            break;
+        }
+
         case kWhatQueueBuffer:
         {
             onQueueBuffer(msg);
@@ -580,6 +595,8 @@
     while (numBytesAvailableToWrite > 0 && !mAudioQueue.empty()) {
         QueueEntry *entry = &*mAudioQueue.begin();
 
+        mLastAudioBufferDrained = entry->mBufferOrdinal;
+
         if (entry->mBuffer == NULL) {
             // EOS
             int64_t postEOSDelayUs = 0;
@@ -716,6 +733,25 @@
         } else {
             realTimeUs = getRealTimeUs(mediaTimeUs, nowUs);
         }
+
+        // Heuristics to handle situation when media time changed without a
+        // discontinuity. If we have not drained an audio buffer that was
+        // received after this buffer, repost in 10 msec. Otherwise repost
+        // in 500 msec.
+        delayUs = realTimeUs - nowUs;
+        if (delayUs > 500000) {
+            int64_t postDelayUs = 500000;
+            if (mHasAudio && (mLastAudioBufferDrained - entry.mBufferOrdinal) <= 0) {
+                postDelayUs = 10000;
+            }
+            msg->setWhat(kWhatPostDrainVideoQueue);
+            msg->post(postDelayUs);
+            mVideoScheduler->restart();
+            ALOGI("possible video time jump of %dms, retrying in %dms",
+                    (int)(delayUs / 1000), (int)(postDelayUs / 1000));
+            mDrainVideoQueuePending = true;
+            return;
+        }
     }
 
     realTimeUs = mVideoScheduler->schedule(realTimeUs * 1000) / 1000;
@@ -855,6 +891,7 @@
     entry.mNotifyConsumed = notifyConsumed;
     entry.mOffset = 0;
     entry.mFinalResult = OK;
+    entry.mBufferOrdinal = ++mTotalBuffersQueued;
 
     if (audio) {
         Mutex::Autolock autoLock(mLock);
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h
index 7079f85..b15a266 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h
@@ -101,6 +101,7 @@
     enum {
         kWhatDrainAudioQueue     = 'draA',
         kWhatDrainVideoQueue     = 'draV',
+        kWhatPostDrainVideoQueue = 'pDVQ',
         kWhatQueueBuffer         = 'queB',
         kWhatQueueEOS            = 'qEOS',
         kWhatFlush               = 'flus',
@@ -119,6 +120,7 @@
         sp<AMessage> mNotifyConsumed;
         size_t mOffset;
         status_t mFinalResult;
+        int32_t mBufferOrdinal;
     };
 
     static const int64_t kMinPositionUpdateDelayUs;
@@ -169,6 +171,9 @@
     bool mAudioOffloadTornDown;
     audio_offload_info_t mCurrentOffloadInfo;
 
+    int32_t mTotalBuffersQueued;
+    int32_t mLastAudioBufferDrained;
+
     size_t fillAudioBuffer(void *buffer, size_t size);
 
     bool onDrainAudioQueue();