stagefright: add support for limiting framerate in GraphicBufferSource

Bug: 19014096
Change-Id: I6de781e4d140a247dfd8fd8f12c3ddd7baa39ad4
diff --git a/include/media/IOMX.h b/include/media/IOMX.h
index 627f23b..6def65b 100644
--- a/include/media/IOMX.h
+++ b/include/media/IOMX.h
@@ -147,6 +147,7 @@
         INTERNAL_OPTION_SUSPEND,  // data is a bool
         INTERNAL_OPTION_REPEAT_PREVIOUS_FRAME_DELAY,  // data is an int64_t
         INTERNAL_OPTION_MAX_TIMESTAMP_GAP, // data is int64_t
+        INTERNAL_OPTION_MAX_FPS, // data is float
         INTERNAL_OPTION_START_TIME, // data is an int64_t
         INTERNAL_OPTION_TIME_LAPSE, // data is an int64_t[2]
     };
diff --git a/include/media/stagefright/ACodec.h b/include/media/stagefright/ACodec.h
index 595ace8..442c861 100644
--- a/include/media/stagefright/ACodec.h
+++ b/include/media/stagefright/ACodec.h
@@ -214,6 +214,7 @@
 
     int64_t mRepeatFrameDelayUs;
     int64_t mMaxPtsGapUs;
+    float mMaxFps;
 
     int64_t mTimePerFrameUs;
     int64_t mTimePerCaptureUs;
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index c8806ae..7d313e0 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -419,6 +419,7 @@
       mMetaDataBuffersToSubmit(0),
       mRepeatFrameDelayUs(-1ll),
       mMaxPtsGapUs(-1ll),
+      mMaxFps(-1),
       mTimePerFrameUs(-1ll),
       mTimePerCaptureUs(-1ll),
       mCreateInputBuffersSuspended(false),
@@ -1259,6 +1260,10 @@
             mMaxPtsGapUs = -1ll;
         }
 
+        if (!msg->findFloat("max-fps-to-encoder", &mMaxFps)) {
+            mMaxFps = -1;
+        }
+
         if (!msg->findInt64("time-lapse", &mTimePerCaptureUs)) {
             mTimePerCaptureUs = -1ll;
         }
@@ -5110,6 +5115,21 @@
         }
     }
 
+    if (err == OK && mCodec->mMaxFps > 0) {
+        err = mCodec->mOMX->setInternalOption(
+                mCodec->mNode,
+                kPortIndexInput,
+                IOMX::INTERNAL_OPTION_MAX_FPS,
+                &mCodec->mMaxFps,
+                sizeof(mCodec->mMaxFps));
+
+        if (err != OK) {
+            ALOGE("[%s] Unable to configure max fps (err %d)",
+                    mCodec->mComponentName.c_str(),
+                    err);
+        }
+    }
+
     if (err == OK && mCodec->mTimePerCaptureUs > 0ll
             && mCodec->mTimePerFrameUs > 0ll) {
         int64_t timeLapse[2];
diff --git a/media/libstagefright/omx/Android.mk b/media/libstagefright/omx/Android.mk
index aaa8334..be8cf46 100644
--- a/media/libstagefright/omx/Android.mk
+++ b/media/libstagefright/omx/Android.mk
@@ -6,6 +6,7 @@
 endif
 
 LOCAL_SRC_FILES:=                     \
+        FrameDropper.cpp              \
         GraphicBufferSource.cpp       \
         OMX.cpp                       \
         OMXMaster.cpp                 \
diff --git a/media/libstagefright/omx/FrameDropper.cpp b/media/libstagefright/omx/FrameDropper.cpp
new file mode 100644
index 0000000..9fba0b7
--- /dev/null
+++ b/media/libstagefright/omx/FrameDropper.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "FrameDropper"
+#include <utils/Log.h>
+
+#include "FrameDropper.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+
+namespace android {
+
+static const int64_t kMaxJitterUs = 2000;
+
+FrameDropper::FrameDropper()
+    : mDesiredMinTimeUs(-1),
+      mMinIntervalUs(0) {
+}
+
+FrameDropper::~FrameDropper() {
+}
+
+status_t FrameDropper::setMaxFrameRate(float maxFrameRate) {
+    if (maxFrameRate <= 0) {
+        ALOGE("framerate should be positive but got %f.", maxFrameRate);
+        return BAD_VALUE;
+    }
+    mMinIntervalUs = (int64_t) (1000000.0f / maxFrameRate);
+    return OK;
+}
+
+bool FrameDropper::shouldDrop(int64_t timeUs) {
+    if (mMinIntervalUs <= 0) {
+        return false;
+    }
+
+    if (mDesiredMinTimeUs < 0) {
+        mDesiredMinTimeUs = timeUs + mMinIntervalUs;
+        ALOGV("first frame %lld, next desired frame %lld", timeUs, mDesiredMinTimeUs);
+        return false;
+    }
+
+    if (timeUs < (mDesiredMinTimeUs - kMaxJitterUs)) {
+        ALOGV("drop frame %lld, desired frame %lld, diff %lld",
+                timeUs, mDesiredMinTimeUs, mDesiredMinTimeUs - timeUs);
+        return true;
+    }
+
+    int64_t n = (timeUs - mDesiredMinTimeUs + kMaxJitterUs) / mMinIntervalUs;
+    mDesiredMinTimeUs += (n + 1) * mMinIntervalUs;
+    ALOGV("keep frame %lld, next desired frame %lld, diff %lld",
+            timeUs, mDesiredMinTimeUs, mDesiredMinTimeUs - timeUs);
+    return false;
+}
+
+}  // namespace android
diff --git a/media/libstagefright/omx/FrameDropper.h b/media/libstagefright/omx/FrameDropper.h
new file mode 100644
index 0000000..c5a6d4b
--- /dev/null
+++ b/media/libstagefright/omx/FrameDropper.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 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 FRAME_DROPPER_H_
+
+#define FRAME_DROPPER_H_
+
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+#include <media/stagefright/foundation/ABase.h>
+
+namespace android {
+
+struct FrameDropper : public RefBase {
+    // No frames will be dropped until a valid max frame rate is set.
+    FrameDropper();
+
+    // maxFrameRate required to be positive.
+    status_t setMaxFrameRate(float maxFrameRate);
+
+    // Returns false if max frame rate has not been set via setMaxFrameRate.
+    bool shouldDrop(int64_t timeUs);
+
+protected:
+    virtual ~FrameDropper();
+
+private:
+    int64_t mDesiredMinTimeUs;
+    int64_t mMinIntervalUs;
+
+    DISALLOW_EVIL_CONSTRUCTORS(FrameDropper);
+};
+
+}  // namespace android
+
+#endif  // FRAME_DROPPER_H_
diff --git a/media/libstagefright/omx/GraphicBufferSource.cpp b/media/libstagefright/omx/GraphicBufferSource.cpp
index 44c7edc..7afe699 100644
--- a/media/libstagefright/omx/GraphicBufferSource.cpp
+++ b/media/libstagefright/omx/GraphicBufferSource.cpp
@@ -30,6 +30,7 @@
 #include <ui/GraphicBuffer.h>
 
 #include <inttypes.h>
+#include "FrameDropper.h"
 
 namespace android {
 
@@ -53,9 +54,9 @@
     mRepeatAfterUs(-1ll),
     mRepeatLastFrameGeneration(0),
     mRepeatLastFrameTimestamp(-1ll),
-    mLatestSubmittedBufferId(-1),
-    mLatestSubmittedBufferFrameNum(0),
-    mLatestSubmittedBufferUseCount(0),
+    mLatestBufferId(-1),
+    mLatestBufferFrameNum(0),
+    mLatestBufferUseCount(0),
     mRepeatBufferDeferred(false),
     mTimePerCaptureUs(-1ll),
     mTimePerFrameUs(-1ll),
@@ -152,7 +153,7 @@
         mLooper->registerHandler(mReflector);
         mLooper->start();
 
-        if (mLatestSubmittedBufferId >= 0) {
+        if (mLatestBufferId >= 0) {
             sp<AMessage> msg =
                 new AMessage(kWhatRepeatLastFrame, mReflector->id());
 
@@ -287,8 +288,8 @@
         ALOGV("cbi %d matches bq slot %d, handle=%p",
                 cbi, id, mBufferSlot[id]->handle);
 
-        if (id == mLatestSubmittedBufferId) {
-            CHECK_GT(mLatestSubmittedBufferUseCount--, 0);
+        if (id == mLatestBufferId) {
+            CHECK_GT(mLatestBufferUseCount--, 0);
         } else {
             mConsumer->releaseBuffer(id, codecBuffer.mFrameNumber,
                     EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE);
@@ -313,11 +314,11 @@
         ALOGV("buffer freed, EOS pending");
         submitEndOfInputStream_l();
     } else if (mRepeatBufferDeferred) {
-        bool success = repeatLatestSubmittedBuffer_l();
+        bool success = repeatLatestBuffer_l();
         if (success) {
-            ALOGV("deferred repeatLatestSubmittedBuffer_l SUCCESS");
+            ALOGV("deferred repeatLatestBuffer_l SUCCESS");
         } else {
-            ALOGV("deferred repeatLatestSubmittedBuffer_l FAILURE");
+            ALOGV("deferred repeatLatestBuffer_l FAILURE");
         }
         mRepeatBufferDeferred = false;
     }
@@ -382,12 +383,12 @@
     mSuspended = false;
 
     if (mExecuting && mNumFramesAvailable == 0 && mRepeatBufferDeferred) {
-        if (repeatLatestSubmittedBuffer_l()) {
-            ALOGV("suspend/deferred repeatLatestSubmittedBuffer_l SUCCESS");
+        if (repeatLatestBuffer_l()) {
+            ALOGV("suspend/deferred repeatLatestBuffer_l SUCCESS");
 
             mRepeatBufferDeferred = false;
         } else {
-            ALOGV("suspend/deferred repeatLatestSubmittedBuffer_l FAILURE");
+            ALOGV("suspend/deferred repeatLatestBuffer_l FAILURE");
         }
     }
 }
@@ -441,12 +442,22 @@
 
     // only submit sample if start time is unspecified, or sample
     // is queued after the specified start time
+    bool dropped = false;
     if (mSkipFramesBeforeNs < 0ll || item.mTimestamp >= mSkipFramesBeforeNs) {
         // if start time is set, offset time stamp by start time
         if (mSkipFramesBeforeNs > 0) {
             item.mTimestamp -= mSkipFramesBeforeNs;
         }
-        err = submitBuffer_l(item, cbi);
+
+        int64_t timeUs = item.mTimestamp / 1000;
+        if (mFrameDropper != NULL && mFrameDropper->shouldDrop(timeUs)) {
+            ALOGV("skipping frame (%lld) to meet max framerate", static_cast<long long>(timeUs));
+            // set err to OK so that the skipped frame can still be saved as the lastest frame
+            err = OK;
+            dropped = true;
+        } else {
+            err = submitBuffer_l(item, cbi);
+        }
     }
 
     if (err != OK) {
@@ -455,46 +466,46 @@
                 EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE);
     } else {
         ALOGV("buffer submitted (bq %d, cbi %d)", item.mBuf, cbi);
-        setLatestSubmittedBuffer_l(item);
+        setLatestBuffer_l(item, dropped);
     }
 
     return true;
 }
 
-bool GraphicBufferSource::repeatLatestSubmittedBuffer_l() {
+bool GraphicBufferSource::repeatLatestBuffer_l() {
     CHECK(mExecuting && mNumFramesAvailable == 0);
 
-    if (mLatestSubmittedBufferId < 0 || mSuspended) {
+    if (mLatestBufferId < 0 || mSuspended) {
         return false;
     }
-    if (mBufferSlot[mLatestSubmittedBufferId] == NULL) {
+    if (mBufferSlot[mLatestBufferId] == NULL) {
         // This can happen if the remote side disconnects, causing
         // onBuffersReleased() to NULL out our copy of the slots.  The
         // buffer is gone, so we have nothing to show.
         //
         // To be on the safe side we try to release the buffer.
-        ALOGD("repeatLatestSubmittedBuffer_l: slot was NULL");
+        ALOGD("repeatLatestBuffer_l: slot was NULL");
         mConsumer->releaseBuffer(
-                mLatestSubmittedBufferId,
-                mLatestSubmittedBufferFrameNum,
+                mLatestBufferId,
+                mLatestBufferFrameNum,
                 EGL_NO_DISPLAY,
                 EGL_NO_SYNC_KHR,
                 Fence::NO_FENCE);
-        mLatestSubmittedBufferId = -1;
-        mLatestSubmittedBufferFrameNum = 0;
+        mLatestBufferId = -1;
+        mLatestBufferFrameNum = 0;
         return false;
     }
 
     int cbi = findAvailableCodecBuffer_l();
     if (cbi < 0) {
         // No buffers available, bail.
-        ALOGV("repeatLatestSubmittedBuffer_l: no codec buffers.");
+        ALOGV("repeatLatestBuffer_l: no codec buffers.");
         return false;
     }
 
     BufferQueue::BufferItem item;
-    item.mBuf = mLatestSubmittedBufferId;
-    item.mFrameNumber = mLatestSubmittedBufferFrameNum;
+    item.mBuf = mLatestBufferId;
+    item.mFrameNumber = mLatestBufferFrameNum;
     item.mTimestamp = mRepeatLastFrameTimestamp;
 
     status_t err = submitBuffer_l(item, cbi);
@@ -503,7 +514,7 @@
         return false;
     }
 
-    ++mLatestSubmittedBufferUseCount;
+    ++mLatestBufferUseCount;
 
     /* repeat last frame up to kRepeatLastFrameCount times.
      * in case of static scene, a single repeat might not get rid of encoder
@@ -522,26 +533,26 @@
     return true;
 }
 
-void GraphicBufferSource::setLatestSubmittedBuffer_l(
-        const BufferQueue::BufferItem &item) {
-    ALOGV("setLatestSubmittedBuffer_l");
+void GraphicBufferSource::setLatestBuffer_l(
+        const BufferQueue::BufferItem &item, bool dropped) {
+    ALOGV("setLatestBuffer_l");
 
-    if (mLatestSubmittedBufferId >= 0) {
-        if (mLatestSubmittedBufferUseCount == 0) {
+    if (mLatestBufferId >= 0) {
+        if (mLatestBufferUseCount == 0) {
             mConsumer->releaseBuffer(
-                    mLatestSubmittedBufferId,
-                    mLatestSubmittedBufferFrameNum,
+                    mLatestBufferId,
+                    mLatestBufferFrameNum,
                     EGL_NO_DISPLAY,
                     EGL_NO_SYNC_KHR,
                     Fence::NO_FENCE);
         }
     }
 
-    mLatestSubmittedBufferId = item.mBuf;
-    mLatestSubmittedBufferFrameNum = item.mFrameNumber;
+    mLatestBufferId = item.mBuf;
+    mLatestBufferFrameNum = item.mFrameNumber;
     mRepeatLastFrameTimestamp = item.mTimestamp + mRepeatAfterUs * 1000;
 
-    mLatestSubmittedBufferUseCount = 1;
+    mLatestBufferUseCount = dropped ? 0 : 1;
     mRepeatBufferDeferred = false;
     mRepeatLastFrameCount = kRepeatLastFrameCount;
 
@@ -841,6 +852,23 @@
     return OK;
 }
 
+status_t GraphicBufferSource::setMaxFps(float maxFps) {
+    Mutex::Autolock autoLock(mMutex);
+
+    if (mExecuting) {
+        return INVALID_OPERATION;
+    }
+
+    mFrameDropper = new FrameDropper();
+    status_t err = mFrameDropper->setMaxFrameRate(maxFps);
+    if (err != OK) {
+        mFrameDropper.clear();
+        return err;
+    }
+
+    return OK;
+}
+
 void GraphicBufferSource::setSkipFramesBeforeUs(int64_t skipFramesBeforeUs) {
     Mutex::Autolock autoLock(mMutex);
 
@@ -879,12 +907,12 @@
                 break;
             }
 
-            bool success = repeatLatestSubmittedBuffer_l();
+            bool success = repeatLatestBuffer_l();
 
             if (success) {
-                ALOGV("repeatLatestSubmittedBuffer_l SUCCESS");
+                ALOGV("repeatLatestBuffer_l SUCCESS");
             } else {
-                ALOGV("repeatLatestSubmittedBuffer_l FAILURE");
+                ALOGV("repeatLatestBuffer_l FAILURE");
                 mRepeatBufferDeferred = true;
             }
             break;
diff --git a/media/libstagefright/omx/GraphicBufferSource.h b/media/libstagefright/omx/GraphicBufferSource.h
index c8e3775..ce3881e 100644
--- a/media/libstagefright/omx/GraphicBufferSource.h
+++ b/media/libstagefright/omx/GraphicBufferSource.h
@@ -30,6 +30,8 @@
 
 namespace android {
 
+class FrameDropper;
+
 /*
  * This class is used to feed OMX codecs from a Surface via BufferQueue.
  *
@@ -119,6 +121,9 @@
     // of suspension on input.
     status_t setMaxTimestampGapUs(int64_t maxGapUs);
 
+    // When set, the max frame rate fed to the encoder will be capped at maxFps.
+    status_t setMaxFps(float maxFps);
+
     // Sets the time lapse (or slow motion) parameters.
     // data[0] is the time (us) between two frames for playback
     // data[1] is the time (us) between two frames for capture
@@ -193,8 +198,8 @@
     // doing anything if we don't have a codec buffer available.
     void submitEndOfInputStream_l();
 
-    void setLatestSubmittedBuffer_l(const BufferQueue::BufferItem &item);
-    bool repeatLatestSubmittedBuffer_l();
+    void setLatestBuffer_l(const BufferQueue::BufferItem &item, bool dropped);
+    bool repeatLatestBuffer_l();
     int64_t getTimestamp(const BufferQueue::BufferItem &item);
 
     // Lock, covers all member variables.
@@ -250,6 +255,8 @@
     int64_t mPrevModifiedTimeUs;
     int64_t mSkipFramesBeforeNs;
 
+    sp<FrameDropper> mFrameDropper;
+
     sp<ALooper> mLooper;
     sp<AHandlerReflector<GraphicBufferSource> > mReflector;
 
@@ -258,11 +265,11 @@
     int64_t mRepeatLastFrameTimestamp;
     int32_t mRepeatLastFrameCount;
 
-    int mLatestSubmittedBufferId;
-    uint64_t mLatestSubmittedBufferFrameNum;
-    int32_t mLatestSubmittedBufferUseCount;
+    int mLatestBufferId;
+    uint64_t mLatestBufferFrameNum;
+    int32_t mLatestBufferUseCount;
 
-    // The previously submitted buffer should've been repeated but
+    // The previous buffer should've been repeated but
     // no codec buffer was available at the time.
     bool mRepeatBufferDeferred;
 
diff --git a/media/libstagefright/omx/OMXNodeInstance.cpp b/media/libstagefright/omx/OMXNodeInstance.cpp
index c04d95f..bf59460 100644
--- a/media/libstagefright/omx/OMXNodeInstance.cpp
+++ b/media/libstagefright/omx/OMXNodeInstance.cpp
@@ -1075,6 +1075,7 @@
         case IOMX::INTERNAL_OPTION_REPEAT_PREVIOUS_FRAME_DELAY:
             return "REPEAT_PREVIOUS_FRAME_DELAY";
         case IOMX::INTERNAL_OPTION_MAX_TIMESTAMP_GAP: return "MAX_TIMESTAMP_GAP";
+        case IOMX::INTERNAL_OPTION_MAX_FPS:           return "MAX_FPS";
         case IOMX::INTERNAL_OPTION_START_TIME:        return "START_TIME";
         case IOMX::INTERNAL_OPTION_TIME_LAPSE:        return "TIME_LAPSE";
         default:                                      return def;
@@ -1092,6 +1093,7 @@
         case IOMX::INTERNAL_OPTION_SUSPEND:
         case IOMX::INTERNAL_OPTION_REPEAT_PREVIOUS_FRAME_DELAY:
         case IOMX::INTERNAL_OPTION_MAX_TIMESTAMP_GAP:
+        case IOMX::INTERNAL_OPTION_MAX_FPS:
         case IOMX::INTERNAL_OPTION_START_TIME:
         case IOMX::INTERNAL_OPTION_TIME_LAPSE:
         {
@@ -1129,6 +1131,14 @@
                 int64_t maxGapUs = *(int64_t *)data;
                 CLOG_CONFIG(setInternalOption, "gapUs=%lld", (long long)maxGapUs);
                 return bufferSource->setMaxTimestampGapUs(maxGapUs);
+            } else if (type == IOMX::INTERNAL_OPTION_MAX_FPS) {
+                if (size != sizeof(float)) {
+                    return INVALID_OPERATION;
+                }
+
+                float maxFps = *(float *)data;
+                CLOG_CONFIG(setInternalOption, "maxFps=%f", maxFps);
+                return bufferSource->setMaxFps(maxFps);
             } else if (type == IOMX::INTERNAL_OPTION_START_TIME) {
                 if (size != sizeof(int64_t)) {
                     return INVALID_OPERATION;
diff --git a/media/libstagefright/omx/tests/Android.mk b/media/libstagefright/omx/tests/Android.mk
index 447b29e..9be637a 100644
--- a/media/libstagefright/omx/tests/Android.mk
+++ b/media/libstagefright/omx/tests/Android.mk
@@ -20,3 +20,21 @@
 LOCAL_32_BIT_ONLY := true
 
 include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := FrameDropper_test
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+	FrameDropper_test.cpp \
+
+LOCAL_SHARED_LIBRARIES := \
+	libstagefright_omx \
+	libutils \
+
+LOCAL_C_INCLUDES := \
+	frameworks/av/media/libstagefright/omx \
+
+include $(BUILD_NATIVE_TEST)
diff --git a/media/libstagefright/omx/tests/FrameDropper_test.cpp b/media/libstagefright/omx/tests/FrameDropper_test.cpp
new file mode 100644
index 0000000..4ac72c4
--- /dev/null
+++ b/media/libstagefright/omx/tests/FrameDropper_test.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "FrameDropper_test"
+#include <utils/Log.h>
+
+#include <gtest/gtest.h>
+
+#include "FrameDropper.h"
+#include <media/stagefright/foundation/ADebug.h>
+
+namespace android {
+
+struct TestFrame {
+  int64_t timeUs;
+  bool shouldDrop;
+};
+
+static const TestFrame testFrames20Fps[] = {
+    {1000000, false}, {1050000, false}, {1100000, false}, {1150000, false},
+    {1200000, false}, {1250000, false}, {1300000, false}, {1350000, false},
+    {1400000, false}, {1450000, false}, {1500000, false}, {1550000, false},
+    {1600000, false}, {1650000, false}, {1700000, false}, {1750000, false},
+    {1800000, false}, {1850000, false}, {1900000, false}, {1950000, false},
+};
+
+static const TestFrame testFrames30Fps[] = {
+    {1000000, false}, {1033333, false}, {1066667, false}, {1100000, false},
+    {1133333, false}, {1166667, false}, {1200000, false}, {1233333, false},
+    {1266667, false}, {1300000, false}, {1333333, false}, {1366667, false},
+    {1400000, false}, {1433333, false}, {1466667, false}, {1500000, false},
+    {1533333, false}, {1566667, false}, {1600000, false}, {1633333, false},
+};
+
+static const TestFrame testFrames40Fps[] = {
+    {1000000, false}, {1025000, true}, {1050000, false}, {1075000, false},
+    {1100000, false}, {1125000, true}, {1150000, false}, {1175000, false},
+    {1200000, false}, {1225000, true}, {1250000, false}, {1275000, false},
+    {1300000, false}, {1325000, true}, {1350000, false}, {1375000, false},
+    {1400000, false}, {1425000, true}, {1450000, false}, {1475000, false},
+};
+
+static const TestFrame testFrames60Fps[] = {
+    {1000000, false}, {1016667, true}, {1033333, false}, {1050000, true},
+    {1066667, false}, {1083333, true}, {1100000, false}, {1116667, true},
+    {1133333, false}, {1150000, true}, {1166667, false}, {1183333, true},
+    {1200000, false}, {1216667, true}, {1233333, false}, {1250000, true},
+    {1266667, false}, {1283333, true}, {1300000, false}, {1316667, true},
+};
+
+static const TestFrame testFramesVariableFps[] = {
+    // 40fps
+    {1000000, false}, {1025000, true}, {1050000, false}, {1075000, false},
+    {1100000, false}, {1125000, true}, {1150000, false}, {1175000, false},
+    {1200000, false}, {1225000, true}, {1250000, false}, {1275000, false},
+    {1300000, false}, {1325000, true}, {1350000, false}, {1375000, false},
+    {1400000, false}, {1425000, true}, {1450000, false}, {1475000, false},
+    // a timestamp jump plus switch to 20fps
+    {2000000, false}, {2050000, false}, {2100000, false}, {2150000, false},
+    {2200000, false}, {2250000, false}, {2300000, false}, {2350000, false},
+    {2400000, false}, {2450000, false}, {2500000, false}, {2550000, false},
+    {2600000, false}, {2650000, false}, {2700000, false}, {2750000, false},
+    {2800000, false}, {2850000, false}, {2900000, false}, {2950000, false},
+    // 60fps
+    {2966667, false}, {2983333, true}, {3000000, false}, {3016667, true},
+    {3033333, false}, {3050000, true}, {3066667, false}, {3083333, true},
+    {3100000, false}, {3116667, true}, {3133333, false}, {3150000, true},
+    {3166667, false}, {3183333, true}, {3200000, false}, {3216667, true},
+    {3233333, false}, {3250000, true}, {3266667, false}, {3283333, true},
+};
+
+static const int kMaxTestJitterUs = 2000;
+// return one of 1000, 0, -1000 as jitter.
+static int GetJitter(size_t i) {
+    return (1 - (i % 3)) * (kMaxTestJitterUs / 2);
+}
+
+class FrameDropperTest : public ::testing::Test {
+public:
+    FrameDropperTest() : mFrameDropper(new FrameDropper()) {
+        EXPECT_EQ(OK, mFrameDropper->setMaxFrameRate(30.0));
+    }
+
+protected:
+    void RunTest(const TestFrame* frames, size_t size) {
+        for (size_t i = 0; i < size; ++i) {
+            int jitter = GetJitter(i);
+            int64_t testTimeUs = frames[i].timeUs + jitter;
+            printf("time %lld, testTime %lld, jitter %d\n", frames[i].timeUs, testTimeUs, jitter);
+            EXPECT_EQ(frames[i].shouldDrop, mFrameDropper->shouldDrop(testTimeUs));
+        }
+    }
+
+    sp<FrameDropper> mFrameDropper;
+};
+
+TEST_F(FrameDropperTest, TestInvalidMaxFrameRate) {
+    EXPECT_NE(OK, mFrameDropper->setMaxFrameRate(-1.0));
+    EXPECT_NE(OK, mFrameDropper->setMaxFrameRate(0));
+}
+
+TEST_F(FrameDropperTest, Test20Fps) {
+    RunTest(testFrames20Fps, ARRAY_SIZE(testFrames20Fps));
+}
+
+TEST_F(FrameDropperTest, Test30Fps) {
+    RunTest(testFrames30Fps, ARRAY_SIZE(testFrames30Fps));
+}
+
+TEST_F(FrameDropperTest, Test40Fps) {
+    RunTest(testFrames40Fps, ARRAY_SIZE(testFrames40Fps));
+}
+
+TEST_F(FrameDropperTest, Test60Fps) {
+    RunTest(testFrames60Fps, ARRAY_SIZE(testFrames60Fps));
+}
+
+TEST_F(FrameDropperTest, TestVariableFps) {
+    RunTest(testFramesVariableFps, ARRAY_SIZE(testFramesVariableFps));
+}
+
+} // namespace android