Support finer seek control on MediaSources.

related-to-bug: 2858448

Change-Id: Ifb4b13b990fd5889113e47e2c62249ac43391fa1
diff --git a/include/media/stagefright/MediaSource.h b/include/media/stagefright/MediaSource.h
index 9cc94c8..a31395e 100644
--- a/include/media/stagefright/MediaSource.h
+++ b/include/media/stagefright/MediaSource.h
@@ -62,14 +62,21 @@
     // a) not request a seek
     // b) not be late, i.e. lateness_us = 0
     struct ReadOptions {
+        enum SeekMode {
+            SEEK_PREVIOUS_SYNC,
+            SEEK_NEXT_SYNC,
+            SEEK_CLOSEST_SYNC,
+            SEEK_CLOSEST,
+        };
+
         ReadOptions();
 
         // Reset everything back to defaults.
         void reset();
 
-        void setSeekTo(int64_t time_us);
+        void setSeekTo(int64_t time_us, SeekMode mode = SEEK_CLOSEST_SYNC);
         void clearSeekTo();
-        bool getSeekTo(int64_t *time_us) const;
+        bool getSeekTo(int64_t *time_us, SeekMode *mode) const;
 
         void setLateBy(int64_t lateness_us);
         int64_t getLateBy() const;
@@ -81,6 +88,7 @@
 
         uint32_t mOptions;
         int64_t mSeekTimeUs;
+        SeekMode mSeekMode;
         int64_t mLatenessUs;
     };
 
diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h
index cdbf483..e631c7f 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -46,6 +46,7 @@
     kKeyIsSyncFrame       = 'sync',  // int32_t (bool)
     kKeyIsCodecConfig     = 'conf',  // int32_t (bool)
     kKeyTime              = 'time',  // int64_t (usecs)
+    kKeyTargetTime        = 'tarT',  // int64_t (usecs)
     kKeyDuration          = 'dura',  // int64_t (usecs)
     kKeyColorFormat       = 'colf',
     kKeyPlatformPrivate   = 'priv',  // pointer
diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h
index 214f43a..79e7a2f 100644
--- a/include/media/stagefright/OMXCodec.h
+++ b/include/media/stagefright/OMXCodec.h
@@ -141,6 +141,8 @@
     bool mNoMoreOutputData;
     bool mOutputPortSettingsHaveChanged;
     int64_t mSeekTimeUs;
+    ReadOptions::SeekMode mSeekMode;
+    int64_t mTargetTimeUs;
 
     MediaBuffer *mLeftOverBuffer;
 
diff --git a/media/libstagefright/AMRExtractor.cpp b/media/libstagefright/AMRExtractor.cpp
index 0fea72e..9fc1d27 100644
--- a/media/libstagefright/AMRExtractor.cpp
+++ b/media/libstagefright/AMRExtractor.cpp
@@ -212,7 +212,8 @@
     *out = NULL;
 
     int64_t seekTimeUs;
-    if (options && options->getSeekTo(&seekTimeUs)) {
+    ReadOptions::SeekMode mode;
+    if (options && options->getSeekTo(&seekTimeUs, &mode)) {
         int64_t seekFrame = seekTimeUs / 20000ll;  // 20ms per frame.
         mCurrentTimeUs = seekFrame * 20000ll;
         mOffset = seekFrame * mFrameSize + (mIsWide ? 9 : 6);
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index ffed74f..236a62b 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -914,7 +914,8 @@
         if (mSeeking) {
             LOGV("seeking to %lld us (%.2f secs)", mSeekTimeUs, mSeekTimeUs / 1E6);
 
-            options.setSeekTo(mSeekTimeUs);
+            options.setSeekTo(
+                    mSeekTimeUs, MediaSource::ReadOptions::SEEK_CLOSEST_SYNC);
         }
         for (;;) {
             status_t err = mVideoSource->read(&mVideoBuffer, &options);
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index 6f4c980..4cc176a 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -267,7 +267,8 @@
     *buffer = NULL;
 
     int64_t seekTimeUs;
-    if (options && options->getSeekTo(&seekTimeUs)) {
+    ReadOptions::SeekMode mode;
+    if (options && options->getSeekTo(&seekTimeUs, &mode)) {
         return ERROR_UNSUPPORTED;
     }
 
diff --git a/media/libstagefright/JPEGSource.cpp b/media/libstagefright/JPEGSource.cpp
index a4be2dd..ec81097 100644
--- a/media/libstagefright/JPEGSource.cpp
+++ b/media/libstagefright/JPEGSource.cpp
@@ -112,7 +112,8 @@
     *out = NULL;
 
     int64_t seekTimeUs;
-    if (options != NULL && options->getSeekTo(&seekTimeUs)) {
+    ReadOptions::SeekMode mode;
+    if (options != NULL && options->getSeekTo(&seekTimeUs, &mode)) {
         return UNKNOWN_ERROR;
     }
 
diff --git a/media/libstagefright/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp
index 69c3a72..2248e23 100644
--- a/media/libstagefright/MP3Extractor.cpp
+++ b/media/libstagefright/MP3Extractor.cpp
@@ -586,7 +586,8 @@
     *out = NULL;
 
     int64_t seekTimeUs;
-    if (options != NULL && options->getSeekTo(&seekTimeUs)) {
+    ReadOptions::SeekMode mode;
+    if (options != NULL && options->getSeekTo(&seekTimeUs, &mode)) {
         int32_t bitrate;
         if (!mMeta->findInt32(kKeyBitRate, &bitrate)) {
             // bitrate is in bits/sec.
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 0c2f1e6..7cea629 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -1468,12 +1468,45 @@
 
     *out = NULL;
 
+    int64_t targetSampleTimeUs = -1;
+
     int64_t seekTimeUs;
-    if (options && options->getSeekTo(&seekTimeUs)) {
+    ReadOptions::SeekMode mode;
+    if (options && options->getSeekTo(&seekTimeUs, &mode)) {
+        uint32_t findFlags = 0;
+        switch (mode) {
+            case ReadOptions::SEEK_PREVIOUS_SYNC:
+                findFlags = SampleTable::kFlagBefore;
+                break;
+            case ReadOptions::SEEK_NEXT_SYNC:
+                findFlags = SampleTable::kFlagAfter;
+                break;
+            case ReadOptions::SEEK_CLOSEST_SYNC:
+            case ReadOptions::SEEK_CLOSEST:
+                findFlags = SampleTable::kFlagClosest;
+                break;
+            default:
+                CHECK(!"Should not be here.");
+                break;
+        }
+
         uint32_t sampleIndex;
-        status_t err = mSampleTable->findClosestSample(
+        status_t err = mSampleTable->findSampleAtTime(
                 seekTimeUs * mTimescale / 1000000,
-                &sampleIndex, SampleTable::kSyncSample_Flag);
+                &sampleIndex, findFlags);
+
+        if (mode == ReadOptions::SEEK_CLOSEST) {
+            // We found the closest sample already, now we want the sync
+            // sample preceding it (or the sample itself of course), even
+            // if the subsequent sync sample is closer.
+            findFlags = SampleTable::kFlagBefore;
+        }
+
+        uint32_t syncSampleIndex;
+        if (err == OK) {
+            err = mSampleTable->findSyncSampleNear(
+                    sampleIndex, &syncSampleIndex, findFlags);
+        }
 
         if (err != OK) {
             if (err == ERROR_OUT_OF_RANGE) {
@@ -1487,7 +1520,27 @@
             return err;
         }
 
-        mCurrentSampleIndex = sampleIndex;
+        uint32_t sampleTime;
+        CHECK_EQ(OK, mSampleTable->getMetaDataForSample(
+                    sampleIndex, NULL, NULL, &sampleTime));
+
+        if (mode == ReadOptions::SEEK_CLOSEST) {
+            targetSampleTimeUs = (sampleTime * 1000000ll) / mTimescale;
+        }
+
+#if 0
+        uint32_t syncSampleTime;
+        CHECK_EQ(OK, mSampleTable->getMetaDataForSample(
+                    syncSampleIndex, NULL, NULL, &syncSampleTime));
+
+        LOGI("seek to time %lld us => sample at time %lld us, "
+             "sync sample at time %lld us",
+             seekTimeUs,
+             sampleTime * 1000000ll / mTimescale,
+             syncSampleTime * 1000000ll / mTimescale);
+#endif
+
+        mCurrentSampleIndex = syncSampleIndex;
         if (mBuffer != NULL) {
             mBuffer->release();
             mBuffer = NULL;
@@ -1536,6 +1589,12 @@
             mBuffer->meta_data()->clear();
             mBuffer->meta_data()->setInt64(
                     kKeyTime, ((int64_t)dts * 1000000) / mTimescale);
+
+            if (targetSampleTimeUs >= 0) {
+                mBuffer->meta_data()->setInt64(
+                        kKeyTargetTime, targetSampleTimeUs);
+            }
+
             ++mCurrentSampleIndex;
         }
 
@@ -1632,6 +1691,12 @@
         mBuffer->meta_data()->clear();
         mBuffer->meta_data()->setInt64(
                 kKeyTime, ((int64_t)dts * 1000000) / mTimescale);
+
+        if (targetSampleTimeUs >= 0) {
+            mBuffer->meta_data()->setInt64(
+                    kKeyTargetTime, targetSampleTimeUs);
+        }
+
         ++mCurrentSampleIndex;
 
         *out = mBuffer;
diff --git a/media/libstagefright/MediaSource.cpp b/media/libstagefright/MediaSource.cpp
index ec89b74..fd0e79c 100644
--- a/media/libstagefright/MediaSource.cpp
+++ b/media/libstagefright/MediaSource.cpp
@@ -34,18 +34,22 @@
     mLatenessUs = 0;
 }
 
-void MediaSource::ReadOptions::setSeekTo(int64_t time_us) {
+void MediaSource::ReadOptions::setSeekTo(int64_t time_us, SeekMode mode) {
     mOptions |= kSeekTo_Option;
     mSeekTimeUs = time_us;
+    mSeekMode = mode;
 }
 
 void MediaSource::ReadOptions::clearSeekTo() {
     mOptions &= ~kSeekTo_Option;
     mSeekTimeUs = 0;
+    mSeekMode = SEEK_CLOSEST_SYNC;
 }
 
-bool MediaSource::ReadOptions::getSeekTo(int64_t *time_us) const {
+bool MediaSource::ReadOptions::getSeekTo(
+        int64_t *time_us, SeekMode *mode) const {
     *time_us = mSeekTimeUs;
+    *mode = mSeekMode;
     return (mOptions & kSeekTo_Option) != 0;
 }
 
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 7762bf4..1b63083 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -1272,6 +1272,8 @@
       mNoMoreOutputData(false),
       mOutputPortSettingsHaveChanged(false),
       mSeekTimeUs(-1),
+      mSeekMode(ReadOptions::SEEK_CLOSEST_SYNC),
+      mTargetTimeUs(-1),
       mLeftOverBuffer(NULL),
       mPaused(false) {
     mPortStatus[kPortIndexInput] = ENABLED;
@@ -1640,13 +1642,33 @@
                         kKeyBufferID,
                         msg.u.extended_buffer_data.buffer);
 
-                mFilledBuffers.push_back(i);
-                mBufferFilled.signal();
-
                 if (msg.u.extended_buffer_data.flags & OMX_BUFFERFLAG_EOS) {
                     CODEC_LOGV("No more output data.");
                     mNoMoreOutputData = true;
                 }
+
+                if (mTargetTimeUs >= 0) {
+                    CHECK(msg.u.extended_buffer_data.timestamp <= mTargetTimeUs);
+
+                    if (msg.u.extended_buffer_data.timestamp < mTargetTimeUs) {
+                        CODEC_LOGV(
+                                "skipping output buffer at timestamp %lld us",
+                                msg.u.extended_buffer_data.timestamp);
+
+                        fillOutputBuffer(info);
+                        break;
+                    }
+
+                    CODEC_LOGV(
+                            "returning output buffer at target timestamp "
+                            "%lld us",
+                            msg.u.extended_buffer_data.timestamp);
+
+                    mTargetTimeUs = -1;
+                }
+
+                mFilledBuffers.push_back(i);
+                mBufferFilled.signal();
             }
 
             break;
@@ -2185,12 +2207,24 @@
             }
 
             MediaSource::ReadOptions options;
-            options.setSeekTo(mSeekTimeUs);
+            options.setSeekTo(mSeekTimeUs, mSeekMode);
 
             mSeekTimeUs = -1;
+            mSeekMode = ReadOptions::SEEK_CLOSEST_SYNC;
             mBufferFilled.signal();
 
             err = mSource->read(&srcBuffer, &options);
+
+            if (err == OK) {
+                int64_t targetTimeUs;
+                if (srcBuffer->meta_data()->findInt64(
+                            kKeyTargetTime, &targetTimeUs)
+                        && targetTimeUs >= 0) {
+                    mTargetTimeUs = targetTimeUs;
+                } else {
+                    mTargetTimeUs = -1;
+                }
+            }
         } else if (mLeftOverBuffer) {
             srcBuffer = mLeftOverBuffer;
             mLeftOverBuffer = NULL;
@@ -2239,7 +2273,7 @@
 
         int64_t lastBufferTimeUs;
         CHECK(srcBuffer->meta_data()->findInt64(kKeyTime, &lastBufferTimeUs));
-        CHECK(timestampUs >= 0);
+        CHECK(lastBufferTimeUs >= 0);
 
         if (offset == 0) {
             timestampUs = lastBufferTimeUs;
@@ -2696,6 +2730,8 @@
     mNoMoreOutputData = false;
     mOutputPortSettingsHaveChanged = false;
     mSeekTimeUs = -1;
+    mSeekMode = ReadOptions::SEEK_CLOSEST_SYNC;
+    mTargetTimeUs = -1;
     mFilledBuffers.clear();
     mPaused = false;
 
@@ -2790,7 +2826,8 @@
 
     bool seeking = false;
     int64_t seekTimeUs;
-    if (options && options->getSeekTo(&seekTimeUs)) {
+    ReadOptions::SeekMode seekMode;
+    if (options && options->getSeekTo(&seekTimeUs, &seekMode)) {
         seeking = true;
     }
 
@@ -2800,6 +2837,7 @@
         if (seeking) {
             CHECK(seekTimeUs >= 0);
             mSeekTimeUs = seekTimeUs;
+            mSeekMode = seekMode;
 
             // There's no reason to trigger the code below, there's
             // nothing to flush yet.
@@ -2823,6 +2861,7 @@
 
         CHECK(seekTimeUs >= 0);
         mSeekTimeUs = seekTimeUs;
+        mSeekMode = seekMode;
 
         mFilledBuffers.clear();
 
diff --git a/media/libstagefright/OggExtractor.cpp b/media/libstagefright/OggExtractor.cpp
index 6013b6f..c267a23 100644
--- a/media/libstagefright/OggExtractor.cpp
+++ b/media/libstagefright/OggExtractor.cpp
@@ -155,7 +155,8 @@
     *out = NULL;
 
     int64_t seekTimeUs;
-    if (options && options->getSeekTo(&seekTimeUs)) {
+    ReadOptions::SeekMode mode;
+    if (options && options->getSeekTo(&seekTimeUs, &mode)) {
         off_t pos = seekTimeUs * mExtractor->mImpl->approxBitrate() / 8000000ll;
         LOGI("seeking to offset %ld", pos);
 
diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp
index 89a522e..2e62f9f 100644
--- a/media/libstagefright/SampleTable.cpp
+++ b/media/libstagefright/SampleTable.cpp
@@ -314,8 +314,10 @@
     return time1 > time2 ? time1 - time2 : time2 - time1;
 }
 
-status_t SampleTable::findClosestSample(
+status_t SampleTable::findSampleAtTime(
         uint32_t req_time, uint32_t *sample_index, uint32_t flags) {
+    *sample_index = 0;
+
     Mutex::Autolock autoLock(mLock);
 
     uint32_t cur_sample = 0;
@@ -330,16 +332,37 @@
             uint32_t time1 = time + j * delta;
             uint32_t time2 = time1 + delta;
 
+            uint32_t sampleTime;
             if (i+1 == mTimeToSampleCount
                     || (abs_difference(req_time, time1)
                         < abs_difference(req_time, time2))) {
                 *sample_index = cur_sample + j;
+                sampleTime = time1;
             } else {
                 *sample_index = cur_sample + j + 1;
+                sampleTime = time2;
             }
 
-            if (flags & kSyncSample_Flag) {
-                return findClosestSyncSample_l(*sample_index, sample_index);
+            switch (flags) {
+                case kFlagBefore:
+                {
+                    if (sampleTime > req_time && *sample_index > 0) {
+                        --*sample_index;
+                    }
+                    break;
+                }
+
+                case kFlagAfter:
+                {
+                    if (sampleTime < req_time
+                            && *sample_index + 1 < mNumSampleSizes) {
+                        ++*sample_index;
+                    }
+                    break;
+                }
+
+                default:
+                    break;
             }
 
             return OK;
@@ -352,8 +375,10 @@
     return ERROR_OUT_OF_RANGE;
 }
 
-status_t SampleTable::findClosestSyncSample_l(
-        uint32_t start_sample_index, uint32_t *sample_index) {
+status_t SampleTable::findSyncSampleNear(
+        uint32_t start_sample_index, uint32_t *sample_index, uint32_t flags) {
+    Mutex::Autolock autoLock(mLock);
+
     *sample_index = 0;
 
     if (mSyncSampleOffset < 0) {
@@ -362,29 +387,124 @@
         return OK;
     }
 
-    uint32_t x;
-    uint32_t left = 0;
-    uint32_t right = mNumSyncSamples;
-    while (left < right) {
-        uint32_t mid = (left + right) / 2;
+    if (mNumSyncSamples == 0) {
+        *sample_index = 0;
+        return OK;
+    }
 
+    uint32_t left = 0;
+    while (left < mNumSyncSamples) {
+        uint32_t x;
         if (mDataSource->readAt(
-                    mSyncSampleOffset + 8 + mid * 4, &x, 4) != 4) {
+                    mSyncSampleOffset + 8 + left * 4, &x, 4) != 4) {
             return ERROR_IO;
         }
 
         x = ntohl(x);
+        --x;
 
-        if (x < (start_sample_index + 1)) {
-            left = mid + 1;
-        } else if (x > (start_sample_index + 1)) {
-            right = mid;
-        } else {
+        if (x >= start_sample_index) {
             break;
         }
+
+        ++left;
+    }
+
+    --left;
+    uint32_t x;
+    if (mDataSource->readAt(
+                mSyncSampleOffset + 8 + left * 4, &x, 4) != 4) {
+        return ERROR_IO;
+    }
+
+    x = ntohl(x);
+    --x;
+
+    if (left + 1 < mNumSyncSamples) {
+        uint32_t y;
+        if (mDataSource->readAt(
+                    mSyncSampleOffset + 8 + (left + 1) * 4, &y, 4) != 4) {
+            return ERROR_IO;
+        }
+
+        y = ntohl(y);
+        --y;
+
+        // our sample lies between sync samples x and y.
+
+        status_t err = mSampleIterator->seekTo(start_sample_index);
+        if (err != OK) {
+            return err;
+        }
+
+        uint32_t sample_time = mSampleIterator->getSampleTime();
+
+        err = mSampleIterator->seekTo(x);
+        if (err != OK) {
+            return err;
+        }
+        uint32_t x_time = mSampleIterator->getSampleTime();
+
+        err = mSampleIterator->seekTo(y);
+        if (err != OK) {
+            return err;
+        }
+
+        uint32_t y_time = mSampleIterator->getSampleTime();
+
+        if (abs_difference(x_time, sample_time)
+                > abs_difference(y_time, sample_time)) {
+            // Pick the sync sample closest (timewise) to the start-sample.
+            x = y;
+            ++left;
+        }
     }
 
-    *sample_index = x - 1;
+    switch (flags) {
+        case kFlagBefore:
+        {
+            if (x > start_sample_index) {
+                CHECK(left > 0);
+
+                if (mDataSource->readAt(
+                            mSyncSampleOffset + 8 + (left - 1) * 4, &x, 4) != 4) {
+                    return ERROR_IO;
+                }
+
+                x = ntohl(x);
+                --x;
+
+                CHECK(x <= start_sample_index);
+            }
+            break;
+        }
+
+        case kFlagAfter:
+        {
+            if (x < start_sample_index) {
+                if (left + 1 >= mNumSyncSamples) {
+                    return ERROR_OUT_OF_RANGE;
+                }
+
+                if (mDataSource->readAt(
+                            mSyncSampleOffset + 8 + (left + 1) * 4, &x, 4) != 4) {
+                    return ERROR_IO;
+                }
+
+                x = ntohl(x);
+                --x;
+
+                CHECK(x >= start_sample_index);
+            }
+
+            break;
+        }
+
+        default:
+            break;
+    }
+
+    *sample_index = x;
 
     return OK;
 }
diff --git a/media/libstagefright/ShoutcastSource.cpp b/media/libstagefright/ShoutcastSource.cpp
index ec25430..23b7681 100644
--- a/media/libstagefright/ShoutcastSource.cpp
+++ b/media/libstagefright/ShoutcastSource.cpp
@@ -93,7 +93,8 @@
     *out = NULL;
 
     int64_t seekTimeUs;
-    if (options && options->getSeekTo(&seekTimeUs)) {
+    ReadOptions::SeekMode mode;
+    if (options && options->getSeekTo(&seekTimeUs, &mode)) {
         return ERROR_UNSUPPORTED;
     }
 
diff --git a/media/libstagefright/WAVExtractor.cpp b/media/libstagefright/WAVExtractor.cpp
index 7365dfa..7c2b07e 100644
--- a/media/libstagefright/WAVExtractor.cpp
+++ b/media/libstagefright/WAVExtractor.cpp
@@ -282,7 +282,8 @@
     *out = NULL;
 
     int64_t seekTimeUs;
-    if (options != NULL && options->getSeekTo(&seekTimeUs)) {
+    ReadOptions::SeekMode mode;
+    if (options != NULL && options->getSeekTo(&seekTimeUs, &mode)) {
         int64_t pos = (seekTimeUs * mSampleRate) / 1000000 * mNumChannels * 2;
         if (pos > mSize) {
             pos = mSize;
diff --git a/media/libstagefright/codecs/aacdec/AACDecoder.cpp b/media/libstagefright/codecs/aacdec/AACDecoder.cpp
index 8ae1135..c5b51c0 100644
--- a/media/libstagefright/codecs/aacdec/AACDecoder.cpp
+++ b/media/libstagefright/codecs/aacdec/AACDecoder.cpp
@@ -159,7 +159,8 @@
     *out = NULL;
 
     int64_t seekTimeUs;
-    if (options && options->getSeekTo(&seekTimeUs)) {
+    ReadOptions::SeekMode mode;
+    if (options && options->getSeekTo(&seekTimeUs, &mode)) {
         CHECK(seekTimeUs >= 0);
 
         mNumSamplesOutput = 0;
diff --git a/media/libstagefright/codecs/aacenc/AACEncoder.cpp b/media/libstagefright/codecs/aacenc/AACEncoder.cpp
index 2317de6..e8235c2 100644
--- a/media/libstagefright/codecs/aacenc/AACEncoder.cpp
+++ b/media/libstagefright/codecs/aacenc/AACEncoder.cpp
@@ -202,7 +202,8 @@
     *out = NULL;
 
     int64_t seekTimeUs;
-    CHECK(options == NULL || !options->getSeekTo(&seekTimeUs));
+    ReadOptions::SeekMode mode;
+    CHECK(options == NULL || !options->getSeekTo(&seekTimeUs, &mode));
 
     MediaBuffer *buffer;
     CHECK_EQ(mBufferGroup->acquire_buffer(&buffer), OK);
diff --git a/media/libstagefright/codecs/amrnb/dec/AMRNBDecoder.cpp b/media/libstagefright/codecs/amrnb/dec/AMRNBDecoder.cpp
index 7728597..fb300da 100644
--- a/media/libstagefright/codecs/amrnb/dec/AMRNBDecoder.cpp
+++ b/media/libstagefright/codecs/amrnb/dec/AMRNBDecoder.cpp
@@ -117,7 +117,8 @@
     *out = NULL;
 
     int64_t seekTimeUs;
-    if (options && options->getSeekTo(&seekTimeUs)) {
+    ReadOptions::SeekMode mode;
+    if (options && options->getSeekTo(&seekTimeUs, &mode)) {
         CHECK(seekTimeUs >= 0);
 
         mNumSamplesOutput = 0;
diff --git a/media/libstagefright/codecs/amrnb/enc/AMRNBEncoder.cpp b/media/libstagefright/codecs/amrnb/enc/AMRNBEncoder.cpp
index 4c02fe9..c875426 100644
--- a/media/libstagefright/codecs/amrnb/enc/AMRNBEncoder.cpp
+++ b/media/libstagefright/codecs/amrnb/enc/AMRNBEncoder.cpp
@@ -145,7 +145,8 @@
     *out = NULL;
 
     int64_t seekTimeUs;
-    CHECK(options == NULL || !options->getSeekTo(&seekTimeUs));
+    ReadOptions::SeekMode mode;
+    CHECK(options == NULL || !options->getSeekTo(&seekTimeUs, &mode));
 
     while (mNumInputSamples < kNumSamplesPerFrame) {
         if (mInputBuffer == NULL) {
diff --git a/media/libstagefright/codecs/amrwb/AMRWBDecoder.cpp b/media/libstagefright/codecs/amrwb/AMRWBDecoder.cpp
index c17c100..2a21472 100644
--- a/media/libstagefright/codecs/amrwb/AMRWBDecoder.cpp
+++ b/media/libstagefright/codecs/amrwb/AMRWBDecoder.cpp
@@ -135,7 +135,8 @@
     *out = NULL;
 
     int64_t seekTimeUs;
-    if (options && options->getSeekTo(&seekTimeUs)) {
+    ReadOptions::SeekMode seekMode;
+    if (options && options->getSeekTo(&seekTimeUs, &seekMode)) {
         CHECK(seekTimeUs >= 0);
 
         mNumSamplesOutput = 0;
diff --git a/media/libstagefright/codecs/amrwbenc/AMRWBEncoder.cpp b/media/libstagefright/codecs/amrwbenc/AMRWBEncoder.cpp
index 4257c6a..93304d0 100644
--- a/media/libstagefright/codecs/amrwbenc/AMRWBEncoder.cpp
+++ b/media/libstagefright/codecs/amrwbenc/AMRWBEncoder.cpp
@@ -196,7 +196,8 @@
     *out = NULL;
 
     int64_t seekTimeUs;
-    CHECK(options == NULL || !options->getSeekTo(&seekTimeUs));
+    ReadOptions::SeekMode mode;
+    CHECK(options == NULL || !options->getSeekTo(&seekTimeUs, &mode));
 
     while (mNumInputSamples < kNumSamplesPerFrame) {
         if (mInputBuffer == NULL) {
diff --git a/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp b/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp
index 24c361e..050e3da 100644
--- a/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp
+++ b/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp
@@ -51,7 +51,9 @@
       mInputBuffer(NULL),
       mAnchorTimeUs(0),
       mNumSamplesOutput(0),
-      mPendingSeekTimeUs(-1) {
+      mPendingSeekTimeUs(-1),
+      mPendingSeekMode(MediaSource::ReadOptions::SEEK_CLOSEST_SYNC),
+      mTargetTimeUs(-1) {
     memset(mHandle, 0, sizeof(tagAVCHandle));
     mHandle->AVCObject = NULL;
     mHandle->userData = this;
@@ -161,6 +163,8 @@
     mAnchorTimeUs = 0;
     mNumSamplesOutput = 0;
     mPendingSeekTimeUs = -1;
+    mPendingSeekMode = ReadOptions::SEEK_CLOSEST_SYNC;
+    mTargetTimeUs = -1;
     mStarted = true;
 
     return OK;
@@ -229,11 +233,13 @@
     *out = NULL;
 
     int64_t seekTimeUs;
-    if (options && options->getSeekTo(&seekTimeUs)) {
+    ReadOptions::SeekMode mode;
+    if (options && options->getSeekTo(&seekTimeUs, &mode)) {
         LOGV("seek requested to %lld us (%.2f secs)", seekTimeUs, seekTimeUs / 1E6);
 
         CHECK(seekTimeUs >= 0);
         mPendingSeekTimeUs = seekTimeUs;
+        mPendingSeekMode = mode;
 
         if (mInputBuffer) {
             mInputBuffer->release();
@@ -246,6 +252,8 @@
     if (mInputBuffer == NULL) {
         LOGV("fetching new input buffer.");
 
+        bool seeking = false;
+
         if (!mCodecSpecificData.isEmpty()) {
             mInputBuffer = mCodecSpecificData.editItemAt(0);
             mCodecSpecificData.removeAt(0);
@@ -258,7 +266,9 @@
 
                 ReadOptions seekOptions;
                 if (mPendingSeekTimeUs >= 0) {
-                    seekOptions.setSeekTo(mPendingSeekTimeUs);
+                    seeking = true;
+
+                    seekOptions.setSeekTo(mPendingSeekTimeUs, mPendingSeekMode);
                     mPendingSeekTimeUs = -1;
                 }
                 status_t err = mSource->read(&mInputBuffer, &seekOptions);
@@ -276,6 +286,16 @@
                 mInputBuffer = NULL;
             }
         }
+
+        if (seeking) {
+            int64_t targetTimeUs;
+            if (mInputBuffer->meta_data()->findInt64(kKeyTargetTime, &targetTimeUs)
+                    && targetTimeUs >= 0) {
+                mTargetTimeUs = targetTimeUs;
+            } else {
+                mTargetTimeUs = -1;
+            }
+        }
     }
 
     const uint8_t *fragPtr;
@@ -394,9 +414,35 @@
                 CHECK(index >= 0);
                 CHECK(index < (int32_t)mFrames.size());
 
-                *out = mFrames.editItemAt(index);
-                (*out)->set_range(0, (*out)->size());
-                (*out)->add_ref();
+                MediaBuffer *mbuf = mFrames.editItemAt(index);
+
+                bool skipFrame = false;
+
+                if (mTargetTimeUs >= 0) {
+                    int64_t timeUs;
+                    CHECK(mbuf->meta_data()->findInt64(kKeyTime, &timeUs));
+                    CHECK(timeUs <= mTargetTimeUs);
+
+                    if (timeUs < mTargetTimeUs) {
+                        // We're still waiting for the frame with the matching
+                        // timestamp and we won't return the current one.
+                        skipFrame = true;
+
+                        LOGV("skipping frame at %lld us", timeUs);
+                    } else {
+                        LOGV("found target frame at %lld us", timeUs);
+
+                        mTargetTimeUs = -1;
+                    }
+                }
+
+                if (!skipFrame) {
+                    *out = mbuf;
+                    (*out)->set_range(0, (*out)->size());
+                    (*out)->add_ref();
+                } else {
+                    *out = new MediaBuffer(0);
+                }
 
                 // Do _not_ release input buffer yet.
 
diff --git a/media/libstagefright/codecs/m4v_h263/dec/M4vH263Decoder.cpp b/media/libstagefright/codecs/m4v_h263/dec/M4vH263Decoder.cpp
index 8350f7a..0f08f6e 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/M4vH263Decoder.cpp
+++ b/media/libstagefright/codecs/m4v_h263/dec/M4vH263Decoder.cpp
@@ -37,7 +37,8 @@
       mStarted(false),
       mHandle(new tagvideoDecControls),
       mInputBuffer(NULL),
-      mNumSamplesOutput(0) {
+      mNumSamplesOutput(0),
+      mTargetTimeUs(-1) {
 
     LOGV("M4vH263Decoder");
     memset(mHandle, 0, sizeof(tagvideoDecControls));
@@ -146,6 +147,7 @@
     mSource->start();
 
     mNumSamplesOutput = 0;
+    mTargetTimeUs = -1;
     mStarted = true;
 
     return OK;
@@ -175,8 +177,11 @@
         MediaBuffer **out, const ReadOptions *options) {
     *out = NULL;
 
+    bool seeking = false;
     int64_t seekTimeUs;
-    if (options && options->getSeekTo(&seekTimeUs)) {
+    ReadOptions::SeekMode mode;
+    if (options && options->getSeekTo(&seekTimeUs, &mode)) {
+        seeking = true;
         CHECK_EQ(PVResetVideoDecoder(mHandle), PV_TRUE);
     }
 
@@ -186,6 +191,16 @@
         return err;
     }
 
+    if (seeking) {
+        int64_t targetTimeUs;
+        if (inputBuffer->meta_data()->findInt64(kKeyTargetTime, &targetTimeUs)
+                && targetTimeUs >= 0) {
+            mTargetTimeUs = targetTimeUs;
+        } else {
+            mTargetTimeUs = -1;
+        }
+    }
+
     uint8_t *bitstream =
         (uint8_t *) inputBuffer->data() + inputBuffer->range_offset();
 
@@ -221,17 +236,40 @@
         return INFO_FORMAT_CHANGED;
     }
 
-    *out = mFrames[mNumSamplesOutput & 0x01];
-    (*out)->add_ref();
-
     int64_t timeUs;
     CHECK(inputBuffer->meta_data()->findInt64(kKeyTime, &timeUs));
-    (*out)->meta_data()->setInt64(kKeyTime, timeUs);
 
-    ++mNumSamplesOutput;
     inputBuffer->release();
     inputBuffer = NULL;
 
+    bool skipFrame = false;
+
+    if (mTargetTimeUs >= 0) {
+        CHECK(timeUs <= mTargetTimeUs);
+
+        if (timeUs < mTargetTimeUs) {
+            // We're still waiting for the frame with the matching
+            // timestamp and we won't return the current one.
+            skipFrame = true;
+
+            LOGV("skipping frame at %lld us", timeUs);
+        } else {
+            LOGV("found target frame at %lld us", timeUs);
+
+            mTargetTimeUs = -1;
+        }
+    }
+
+    if (skipFrame) {
+        *out = new MediaBuffer(0);
+    } else {
+        *out = mFrames[mNumSamplesOutput & 0x01];
+        (*out)->add_ref();
+        (*out)->meta_data()->setInt64(kKeyTime, timeUs);
+    }
+
+    ++mNumSamplesOutput;
+
     return OK;
 }
 
diff --git a/media/libstagefright/codecs/mp3dec/MP3Decoder.cpp b/media/libstagefright/codecs/mp3dec/MP3Decoder.cpp
index f40bd11..c4a8280 100644
--- a/media/libstagefright/codecs/mp3dec/MP3Decoder.cpp
+++ b/media/libstagefright/codecs/mp3dec/MP3Decoder.cpp
@@ -122,7 +122,8 @@
     *out = NULL;
 
     int64_t seekTimeUs;
-    if (options && options->getSeekTo(&seekTimeUs)) {
+    ReadOptions::SeekMode mode;
+    if (options && options->getSeekTo(&seekTimeUs, &mode)) {
         CHECK(seekTimeUs >= 0);
 
         mNumFramesOutput = 0;
diff --git a/media/libstagefright/codecs/on2/dec/VPXDecoder.cpp b/media/libstagefright/codecs/on2/dec/VPXDecoder.cpp
index bad8956..fbc97f4 100644
--- a/media/libstagefright/codecs/on2/dec/VPXDecoder.cpp
+++ b/media/libstagefright/codecs/on2/dec/VPXDecoder.cpp
@@ -39,7 +39,8 @@
       mStarted(false),
       mBufferSize(0),
       mCtx(NULL),
-      mBufferGroup(NULL) {
+      mBufferGroup(NULL),
+      mTargetTimeUs(-1) {
     sp<MetaData> inputFormat = source->getFormat();
     const char *mime;
     CHECK(inputFormat->findCString(kKeyMIMEType, &mime));
@@ -94,6 +95,8 @@
     mBufferGroup->add_buffer(new MediaBuffer(mBufferSize));
     mBufferGroup->add_buffer(new MediaBuffer(mBufferSize));
 
+    mTargetTimeUs = -1;
+
     mStarted = true;
 
     return OK;
@@ -126,6 +129,13 @@
         MediaBuffer **out, const ReadOptions *options) {
     *out = NULL;
 
+    bool seeking = false;
+    int64_t seekTimeUs;
+    ReadOptions::SeekMode seekMode;
+    if (options && options->getSeekTo(&seekTimeUs, &seekMode)) {
+        seeking = true;
+    }
+
     MediaBuffer *input;
     status_t err = mSource->read(&input, options);
 
@@ -135,6 +145,16 @@
 
     LOGV("read %d bytes from source\n", input->range_length());
 
+    if (seeking) {
+        int64_t targetTimeUs;
+        if (input->meta_data()->findInt64(kKeyTargetTime, &targetTimeUs)
+                && targetTimeUs >= 0) {
+            mTargetTimeUs = targetTimeUs;
+        } else {
+            mTargetTimeUs = -1;
+        }
+    }
+
     if (vpx_codec_decode(
                 (vpx_codec_ctx_t *)mCtx,
                 (uint8_t *)input->data() + input->range_offset(),
@@ -156,6 +176,29 @@
     input->release();
     input = NULL;
 
+    bool skipFrame = false;
+
+    if (mTargetTimeUs >= 0) {
+        CHECK(timeUs <= mTargetTimeUs);
+
+        if (timeUs < mTargetTimeUs) {
+            // We're still waiting for the frame with the matching
+            // timestamp and we won't return the current one.
+            skipFrame = true;
+
+            LOGV("skipping frame at %lld us", timeUs);
+        } else {
+            LOGV("found target frame at %lld us", timeUs);
+
+            mTargetTimeUs = -1;
+        }
+    }
+
+    if (skipFrame) {
+        *out = new MediaBuffer(0);
+        return OK;
+    }
+
     vpx_codec_iter_t iter = NULL;
     vpx_image_t *img = vpx_codec_get_frame((vpx_codec_ctx_t *)mCtx, &iter);
 
diff --git a/media/libstagefright/codecs/vorbis/dec/VorbisDecoder.cpp b/media/libstagefright/codecs/vorbis/dec/VorbisDecoder.cpp
index df3f16a..53f0638 100644
--- a/media/libstagefright/codecs/vorbis/dec/VorbisDecoder.cpp
+++ b/media/libstagefright/codecs/vorbis/dec/VorbisDecoder.cpp
@@ -200,7 +200,8 @@
     *out = NULL;
 
     int64_t seekTimeUs;
-    if (options && options->getSeekTo(&seekTimeUs)) {
+    ReadOptions::SeekMode mode;
+    if (options && options->getSeekTo(&seekTimeUs, &mode)) {
         CHECK(seekTimeUs >= 0);
 
         mNumFramesOutput = 0;
diff --git a/media/libstagefright/include/AVCDecoder.h b/media/libstagefright/include/AVCDecoder.h
index 621aa9a..7810171 100644
--- a/media/libstagefright/include/AVCDecoder.h
+++ b/media/libstagefright/include/AVCDecoder.h
@@ -58,6 +58,9 @@
     int64_t mAnchorTimeUs;
     int64_t mNumSamplesOutput;
     int64_t mPendingSeekTimeUs;
+    MediaSource::ReadOptions::SeekMode mPendingSeekMode;
+
+    int64_t mTargetTimeUs;
 
     void addCodecSpecificData(const uint8_t *data, size_t size);
 
diff --git a/media/libstagefright/include/M4vH263Decoder.h b/media/libstagefright/include/M4vH263Decoder.h
index ec49e80..7d73e30 100644
--- a/media/libstagefright/include/M4vH263Decoder.h
+++ b/media/libstagefright/include/M4vH263Decoder.h
@@ -54,6 +54,7 @@
     MediaBuffer *mInputBuffer;
 
     int64_t mNumSamplesOutput;
+    int64_t mTargetTimeUs;
 
     void allocateFrames(int32_t width, int32_t height);
     void releaseFrames();
diff --git a/media/libstagefright/include/SampleTable.h b/media/libstagefright/include/SampleTable.h
index 533ce84..a2b2c99 100644
--- a/media/libstagefright/include/SampleTable.h
+++ b/media/libstagefright/include/SampleTable.h
@@ -63,11 +63,17 @@
             uint32_t *decodingTime);
 
     enum {
-        kSyncSample_Flag = 1
+        kFlagBefore,
+        kFlagAfter,
+        kFlagClosest
     };
-    status_t findClosestSample(
+    status_t findSampleAtTime(
             uint32_t req_time, uint32_t *sample_index, uint32_t flags);
 
+    status_t findSyncSampleNear(
+            uint32_t start_sample_index, uint32_t *sample_index,
+            uint32_t flags);
+
     status_t findThumbnailSample(uint32_t *sample_index);
 
 protected:
@@ -111,9 +117,6 @@
 
     friend struct SampleIterator;
 
-    status_t findClosestSyncSample_l(
-            uint32_t start_sample_index, uint32_t *sample_index);
-
     status_t getSampleSize_l(uint32_t sample_index, size_t *sample_size);
 
     SampleTable(const SampleTable &);
diff --git a/media/libstagefright/include/VPXDecoder.h b/media/libstagefright/include/VPXDecoder.h
index 550c612..3b8362d 100644
--- a/media/libstagefright/include/VPXDecoder.h
+++ b/media/libstagefright/include/VPXDecoder.h
@@ -48,6 +48,8 @@
     void *mCtx;
     MediaBufferGroup *mBufferGroup;
 
+    int64_t mTargetTimeUs;
+
     sp<MetaData> mFormat;
 
     VPXDecoder(const VPXDecoder &);
diff --git a/media/libstagefright/matroska/MatroskaExtractor.cpp b/media/libstagefright/matroska/MatroskaExtractor.cpp
index 339a7b5..3739be1 100644
--- a/media/libstagefright/matroska/MatroskaExtractor.cpp
+++ b/media/libstagefright/matroska/MatroskaExtractor.cpp
@@ -281,7 +281,8 @@
     *out = NULL;
 
     int64_t seekTimeUs;
-    if (options && options->getSeekTo(&seekTimeUs)) {
+    ReadOptions::SeekMode mode;
+    if (options && options->getSeekTo(&seekTimeUs, &mode)) {
         mBlockIter.seek(seekTimeUs);
     }