NuPlayerRenderer: use timer for video rendering

MediaClock: timer can be adjusted by system time offset
Test: plays many media files
Bug: 65204641
Change-Id: Icd73a06d9b2fe720224ed951db94136f399d82f8
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
index a568a17..50db4c9 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
@@ -1248,82 +1248,48 @@
         return;
     }
 
-    bool needRepostDrainVideoQueue = false;
-    int64_t delayUs;
     int64_t nowUs = ALooper::GetNowUs();
-    int64_t realTimeUs;
     if (mFlags & FLAG_REAL_TIME) {
-        int64_t mediaTimeUs;
-        CHECK(entry.mBuffer->meta()->findInt64("timeUs", &mediaTimeUs));
-        realTimeUs = mediaTimeUs;
-    } else {
-        int64_t mediaTimeUs;
-        CHECK(entry.mBuffer->meta()->findInt64("timeUs", &mediaTimeUs));
+        int64_t realTimeUs;
+        CHECK(entry.mBuffer->meta()->findInt64("timeUs", &realTimeUs));
 
-        {
-            Mutex::Autolock autoLock(mLock);
-            if (mAnchorTimeMediaUs < 0) {
-                mMediaClock->updateAnchor(mediaTimeUs, nowUs, mediaTimeUs);
-                mAnchorTimeMediaUs = mediaTimeUs;
-                realTimeUs = nowUs;
-            } else if (!mVideoSampleReceived) {
-                // Always render the first video frame.
-                realTimeUs = nowUs;
-            } else if (mAudioFirstAnchorTimeMediaUs < 0
-                || mMediaClock->getRealTimeFor(mediaTimeUs, &realTimeUs) == OK) {
-                realTimeUs = getRealTimeUs(mediaTimeUs, nowUs);
-            } else if (mediaTimeUs - mAudioFirstAnchorTimeMediaUs >= 0) {
-                needRepostDrainVideoQueue = true;
-                realTimeUs = nowUs;
-            } else {
-                realTimeUs = nowUs;
-            }
-        }
-        if (!mHasAudio) {
-            // smooth out videos >= 10fps
-            mMediaClock->updateMaxTimeMedia(mediaTimeUs + 100000);
-        }
+        realTimeUs = mVideoScheduler->schedule(realTimeUs * 1000) / 1000;
 
-        // 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;
-        int64_t postDelayUs = -1;
-        if (delayUs > 500000) {
-            postDelayUs = 500000;
-            if (mHasAudio && (mLastAudioBufferDrained - entry.mBufferOrdinal) <= 0) {
-                postDelayUs = 10000;
-            }
-        } else if (needRepostDrainVideoQueue) {
-            // CHECK(mPlaybackRate > 0);
-            // CHECK(mAudioFirstAnchorTimeMediaUs >= 0);
-            // CHECK(mediaTimeUs - mAudioFirstAnchorTimeMediaUs >= 0);
-            postDelayUs = mediaTimeUs - mAudioFirstAnchorTimeMediaUs;
-            postDelayUs /= mPlaybackRate;
-        }
+        int64_t twoVsyncsUs = 2 * (mVideoScheduler->getVsyncPeriod() / 1000);
 
-        if (postDelayUs >= 0) {
-            msg->setWhat(kWhatPostDrainVideoQueue);
-            msg->post(postDelayUs);
-            mVideoScheduler->restart();
-            ALOGI("possible video time jump of %dms (%lld : %lld) or uninitialized media clock,"
-                    " retrying in %dms",
-                    (int)(delayUs / 1000), (long long)mediaTimeUs,
-                    (long long)mAudioFirstAnchorTimeMediaUs, (int)(postDelayUs / 1000));
-            mDrainVideoQueuePending = true;
-            return;
-        }
+        int64_t delayUs = realTimeUs - nowUs;
+
+        ALOGW_IF(delayUs > 500000, "unusually high delayUs: %lld", (long long)delayUs);
+        // post 2 display refreshes before rendering is due
+        msg->post(delayUs > twoVsyncsUs ? delayUs - twoVsyncsUs : 0);
+
+        mDrainVideoQueuePending = true;
+        return;
     }
 
-    realTimeUs = mVideoScheduler->schedule(realTimeUs * 1000) / 1000;
-    int64_t twoVsyncsUs = 2 * (mVideoScheduler->getVsyncPeriod() / 1000);
+    int64_t mediaTimeUs;
+    CHECK(entry.mBuffer->meta()->findInt64("timeUs", &mediaTimeUs));
 
-    delayUs = realTimeUs - nowUs;
+    {
+        Mutex::Autolock autoLock(mLock);
+        if (mAnchorTimeMediaUs < 0) {
+            mMediaClock->updateAnchor(mediaTimeUs, nowUs, mediaTimeUs);
+            mAnchorTimeMediaUs = mediaTimeUs;
+        }
+    }
+    if (!mHasAudio) {
+        // smooth out videos >= 10fps
+        mMediaClock->updateMaxTimeMedia(mediaTimeUs + 100000);
+    }
 
-    ALOGW_IF(delayUs > 500000, "unusually high delayUs: %" PRId64, delayUs);
-    // post 2 display refreshes before rendering is due
-    msg->post(delayUs > twoVsyncsUs ? delayUs - twoVsyncsUs : 0);
+    if (!mVideoSampleReceived || mediaTimeUs < mAudioFirstAnchorTimeMediaUs) {
+        msg->post();
+    } else {
+        int64_t twoVsyncsUs = 2 * (mVideoScheduler->getVsyncPeriod() / 1000);
+
+        // post 2 display refreshes before rendering is due
+        mMediaClock->addTimer(msg, mediaTimeUs, -twoVsyncsUs);
+    }
 
     mDrainVideoQueuePending = true;
 }
@@ -1357,6 +1323,7 @@
 
         realTimeUs = getRealTimeUs(mediaTimeUs, nowUs);
     }
+    realTimeUs = mVideoScheduler->schedule(realTimeUs * 1000) / 1000;
 
     bool tooLate = false;
 
diff --git a/media/libstagefright/MediaClock.cpp b/media/libstagefright/MediaClock.cpp
index 669e09b..15843a2 100644
--- a/media/libstagefright/MediaClock.cpp
+++ b/media/libstagefright/MediaClock.cpp
@@ -17,6 +17,7 @@
 //#define LOG_NDEBUG 0
 #define LOG_TAG "MediaClock"
 #include <utils/Log.h>
+#include <map>
 
 #include <media/stagefright/MediaClock.h>
 
@@ -29,6 +30,12 @@
 // If larger than this threshold, it's treated as discontinuity.
 static const int64_t kAnchorFluctuationAllowedUs = 10000ll;
 
+MediaClock::Timer::Timer(const sp<AMessage> &notify, int64_t mediaTimeUs, int64_t adjustRealUs)
+    : mNotify(notify),
+      mMediaTimeUs(mediaTimeUs),
+      mAdjustRealUs(adjustRealUs) {
+}
+
 MediaClock::MediaClock()
     : mAnchorTimeMediaUs(-1),
       mAnchorTimeRealUs(-1),
@@ -59,8 +66,8 @@
     Mutex::Autolock autoLock(mLock);
     auto it = mTimers.begin();
     while (it != mTimers.end()) {
-        it->second->setInt32("reason", TIMER_REASON_RESET);
-        it->second->post();
+        it->mNotify->setInt32("reason", TIMER_REASON_RESET);
+        it->mNotify->post();
         it = mTimers.erase(it);
     }
     mAnchorTimeMediaUs = -1;
@@ -204,15 +211,26 @@
     return OK;
 }
 
-void MediaClock::addTimer(const sp<AMessage> &notify, int64_t mediaTimeUs) {
+void MediaClock::addTimer(const sp<AMessage> &notify, int64_t mediaTimeUs,
+                          int64_t adjustRealUs) {
     Mutex::Autolock autoLock(mLock);
-    int64_t nextMediaTimeUs = INT64_MAX;
-    if (!mTimers.empty()) {
-        nextMediaTimeUs = mTimers.begin()->first;
+
+    bool updateTimer = (mPlaybackRate != 0.0);
+    if (updateTimer) {
+        auto it = mTimers.begin();
+        while (it != mTimers.end()) {
+            if (((it->mAdjustRealUs - (double)adjustRealUs) * (double)mPlaybackRate
+                + (it->mMediaTimeUs - mediaTimeUs)) <= 0) {
+                updateTimer = false;
+                break;
+            }
+            ++it;
+        }
     }
 
-    mTimers.emplace(mediaTimeUs, notify);
-    if (mediaTimeUs < nextMediaTimeUs) {
+    mTimers.emplace_back(notify, mediaTimeUs, adjustRealUs);
+
+    if (updateTimer) {
         ++mGeneration;
         processTimers_l();
     }
@@ -248,20 +266,51 @@
         return;
     }
 
+    int64_t nextLapseRealUs = INT64_MAX;
+    std::multimap<int64_t, Timer> notifyList;
     auto it = mTimers.begin();
-    while (it != mTimers.end() && it->first <= nowMediaTimeUs) {
-        it->second->setInt32("reason", TIMER_REASON_REACHED);
-        it->second->post();
-        it = mTimers.erase(it);
+    while (it != mTimers.end()) {
+        double diff = it->mAdjustRealUs * (double)mPlaybackRate
+            + it->mMediaTimeUs - nowMediaTimeUs;
+        int64_t diffMediaUs;
+        if (diff > (double)INT64_MAX) {
+            diffMediaUs = INT64_MAX;
+        } else if (diff < (double)INT64_MIN) {
+            diffMediaUs = INT64_MIN;
+        } else {
+            diffMediaUs = diff;
+        }
+
+        if (diffMediaUs <= 0) {
+            notifyList.emplace(diffMediaUs, *it);
+            it = mTimers.erase(it);
+        } else {
+            if (mPlaybackRate != 0.0
+                && (double)diffMediaUs < INT64_MAX * (double)mPlaybackRate) {
+                int64_t targetRealUs = diffMediaUs / (double)mPlaybackRate;
+                if (targetRealUs < nextLapseRealUs) {
+                    nextLapseRealUs = targetRealUs;
+                }
+            }
+            ++it;
+        }
     }
 
-    if (it == mTimers.end() || mPlaybackRate == 0.0 || mAnchorTimeMediaUs < 0) {
+    auto itNotify = notifyList.begin();
+    while (itNotify != notifyList.end()) {
+        itNotify->second.mNotify->setInt32("reason", TIMER_REASON_REACHED);
+        itNotify->second.mNotify->post();
+        itNotify = notifyList.erase(itNotify);
+    }
+
+    if (mTimers.empty() || mPlaybackRate == 0.0 || mAnchorTimeMediaUs < 0
+        || nextLapseRealUs == INT64_MAX) {
         return;
     }
 
     sp<AMessage> msg = new AMessage(kWhatTimeIsUp, this);
     msg->setInt32("generation", mGeneration);
-    msg->post((it->first - nowMediaTimeUs) / (double)mPlaybackRate);
+    msg->post(nextLapseRealUs);
 }
 
 }  // namespace android
diff --git a/media/libstagefright/include/media/stagefright/MediaClock.h b/media/libstagefright/include/media/stagefright/MediaClock.h
index 59f700a..7511913 100644
--- a/media/libstagefright/include/media/stagefright/MediaClock.h
+++ b/media/libstagefright/include/media/stagefright/MediaClock.h
@@ -18,7 +18,7 @@
 
 #define MEDIA_CLOCK_H_
 
-#include <map>
+#include <list>
 #include <media/stagefright/foundation/AHandler.h>
 #include <utils/Mutex.h>
 #include <utils/RefBase.h>
@@ -30,8 +30,7 @@
 struct MediaClock : public AHandler {
     enum {
         TIMER_REASON_REACHED = 0,
-        TIMER_REASON_NO_CLOCK = 1,
-        TIMER_REASON_RESET = 2,
+        TIMER_REASON_RESET = 1,
     };
 
     MediaClock();
@@ -62,7 +61,10 @@
     // The result is saved in |outRealUs|.
     status_t getRealTimeFor(int64_t targetMediaUs, int64_t *outRealUs) const;
 
-    void addTimer(const sp<AMessage> &notify, int64_t mediaTimeUs);
+    // request to set up a timer. The target time is |mediaTimeUs|, adjusted by
+    // system time of |adjustRealUs|. In other words, the wake up time is
+    // mediaTimeUs + (adjustRealUs / playbackRate)
+    void addTimer(const sp<AMessage> &notify, int64_t mediaTimeUs, int64_t adjustRealUs = 0);
 
     void reset();
 
@@ -76,6 +78,13 @@
         kWhatTimeIsUp = 'tIsU',
     };
 
+    struct Timer {
+        Timer(const sp<AMessage> &notify, int64_t mediaTimeUs, int64_t adjustRealUs);
+        const sp<AMessage> mNotify;
+        int64_t mMediaTimeUs;
+        int64_t mAdjustRealUs;
+    };
+
     status_t getMediaTime_l(
             int64_t realUs,
             int64_t *outMediaUs,
@@ -94,7 +103,7 @@
     float mPlaybackRate;
 
     int32_t mGeneration;
-    std::multimap<int64_t, sp<AMessage> > mTimers;
+    std::list<Timer> mTimers;
 
     DISALLOW_EVIL_CONSTRUCTORS(MediaClock);
 };