Merge "heif: cache the entire stream if possible to support seek" into oc-mr1-dev
am: 7124c1985b

Change-Id: I1eb504704ab164858a5ff9ab51b94144db0fa30d
diff --git a/media/libheif/HeifDecoderImpl.cpp b/media/libheif/HeifDecoderImpl.cpp
index 080313c..115baff 100644
--- a/media/libheif/HeifDecoderImpl.cpp
+++ b/media/libheif/HeifDecoderImpl.cpp
@@ -25,6 +25,7 @@
 #include <drm/drm_framework_common.h>
 #include <media/IDataSource.h>
 #include <media/mediametadataretriever.h>
+#include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/MediaSource.h>
 #include <private/media/VideoFrame.h>
 #include <utils/Log.h>
@@ -48,7 +49,8 @@
      * Constructs HeifDataSource; will take ownership of |stream|.
      */
     HeifDataSource(HeifStream* stream)
-        : mStream(stream), mReadPos(0), mEOS(false) {}
+        : mStream(stream), mEOS(false),
+          mCachedOffset(0), mCachedSize(0), mCacheBufferSize(0) {}
 
     ~HeifDataSource() override {}
 
@@ -68,17 +70,25 @@
     }
 
 private:
-    /*
-     * Buffer size for passing the read data to mediaserver. Set to 64K
-     * (which is what MediaDataSource Java API's jni implementation uses).
-     */
     enum {
+        /*
+         * Buffer size for passing the read data to mediaserver. Set to 64K
+         * (which is what MediaDataSource Java API's jni implementation uses).
+         */
         kBufferSize = 64 * 1024,
+        /*
+         * Initial and max cache buffer size.
+         */
+        kInitialCacheBufferSize = 4 * 1024 * 1024,
+        kMaxCacheBufferSize = 64 * 1024 * 1024,
     };
     sp<IMemory> mMemory;
     std::unique_ptr<HeifStream> mStream;
-    off64_t mReadPos;
     bool mEOS;
+    std::unique_ptr<uint8_t> mCache;
+    off64_t mCachedOffset;
+    size_t mCachedSize;
+    size_t mCacheBufferSize;
 };
 
 bool HeifDataSource::init() {
@@ -89,25 +99,29 @@
         ALOGE("Failed to allocate shared memory!");
         return false;
     }
+    mCache.reset(new uint8_t[kInitialCacheBufferSize]);
+    if (mCache.get() == nullptr) {
+        ALOGE("mFailed to allocate cache!");
+        return false;
+    }
+    mCacheBufferSize = kInitialCacheBufferSize;
     return true;
 }
 
 ssize_t HeifDataSource::readAt(off64_t offset, size_t size) {
     ALOGV("readAt: offset=%lld, size=%zu", (long long)offset, size);
 
-    if (size == 0) {
-        return mEOS ? ERROR_END_OF_STREAM : 0;
-    }
-
-    if (offset < mReadPos) {
+    if (offset < mCachedOffset) {
         // try seek, then rewind/skip, fail if none worked
         if (mStream->seek(offset)) {
             ALOGV("readAt: seek to offset=%lld", (long long)offset);
-            mReadPos = offset;
+            mCachedOffset = offset;
+            mCachedSize = 0;
             mEOS = false;
         } else if (mStream->rewind()) {
             ALOGV("readAt: rewind to offset=0");
-            mReadPos = 0;
+            mCachedOffset = 0;
+            mCachedSize = 0;
             mEOS = false;
         } else {
             ALOGE("readAt: couldn't seek or rewind!");
@@ -115,38 +129,127 @@
         }
     }
 
-    if (mEOS) {
+    if (mEOS && (offset < mCachedOffset ||
+                 offset >= (off64_t)(mCachedOffset + mCachedSize))) {
         ALOGV("readAt: EOS");
         return ERROR_END_OF_STREAM;
     }
 
-    if (offset > mReadPos) {
-        // skipping
-        size_t skipSize = offset - mReadPos;
-        size_t bytesSkipped = mStream->read(nullptr, skipSize);
-        if (bytesSkipped <= skipSize) {
-            mReadPos += bytesSkipped;
-        }
-        if (bytesSkipped != skipSize) {
-            mEOS = true;
-            return ERROR_END_OF_STREAM;
-        }
+    // at this point, offset must be >= mCachedOffset, other cases should
+    // have been caught above.
+    CHECK(offset >= mCachedOffset);
+
+    if (size == 0) {
+        return 0;
     }
 
+    // Can only read max of kBufferSize
     if (size > kBufferSize) {
         size = kBufferSize;
     }
-    size_t bytesRead = mStream->read(mMemory->pointer(), size);
-    if (bytesRead > size || bytesRead == 0) {
+
+    // copy from cache if the request falls entirely in cache
+    if (offset + size <= mCachedOffset + mCachedSize) {
+        memcpy(mMemory->pointer(), mCache.get() + offset - mCachedOffset, size);
+        return size;
+    }
+
+    // need to fetch more, check if we need to expand the cache buffer.
+    if ((off64_t)(offset + size) > mCachedOffset + kMaxCacheBufferSize) {
+        // it's reaching max cache buffer size, need to roll window, and possibly
+        // expand the cache buffer.
+        size_t newCacheBufferSize = mCacheBufferSize;
+        std::unique_ptr<uint8_t> newCache;
+        uint8_t* dst = mCache.get();
+        if (newCacheBufferSize < kMaxCacheBufferSize) {
+            newCacheBufferSize = kMaxCacheBufferSize;
+            newCache.reset(new uint8_t[newCacheBufferSize]);
+            dst = newCache.get();
+        }
+
+        // when rolling the cache window, try to keep about half the old bytes
+        // in case that the client goes back.
+        off64_t newCachedOffset = offset - (off64_t)(newCacheBufferSize / 2);
+        if (newCachedOffset < mCachedOffset) {
+            newCachedOffset = mCachedOffset;
+        }
+
+        int64_t newCachedSize = (int64_t)(mCachedOffset + mCachedSize) - newCachedOffset;
+        if (newCachedSize > 0) {
+            // in this case, the new cache region partially overlop the old cache,
+            // move the portion of the cache we want to save to the beginning of
+            // the cache buffer.
+            memcpy(dst, mCache.get() + newCachedOffset - mCachedOffset, newCachedSize);
+        } else if (newCachedSize < 0){
+            // in this case, the new cache region is entirely out of the old cache,
+            // in order to guarantee sequential read, we need to skip a number of
+            // bytes before reading.
+            size_t bytesToSkip = -newCachedSize;
+            size_t bytesSkipped = mStream->read(nullptr, bytesToSkip);
+            if (bytesSkipped != bytesToSkip) {
+                // bytesSkipped is invalid, there is not enough bytes to reach
+                // the requested offset.
+                ALOGE("readAt: skip failed, EOS");
+
+                mEOS = true;
+                mCachedOffset = newCachedOffset;
+                mCachedSize = 0;
+                return ERROR_END_OF_STREAM;
+            }
+            // set cache size to 0, since we're not keeping any old cache
+            newCachedSize = 0;
+        }
+
+        if (newCache.get() != nullptr) {
+            mCache.reset(newCache.release());
+            mCacheBufferSize = newCacheBufferSize;
+        }
+        mCachedOffset = newCachedOffset;
+        mCachedSize = newCachedSize;
+
+        ALOGV("readAt: rolling cache window to (%lld, %zu), cache buffer size %zu",
+                (long long)mCachedOffset, mCachedSize, mCacheBufferSize);
+    } else {
+        // expand cache buffer, but no need to roll the window
+        size_t newCacheBufferSize = mCacheBufferSize;
+        while (offset + size > mCachedOffset + newCacheBufferSize) {
+            newCacheBufferSize *= 2;
+        }
+        CHECK(newCacheBufferSize <= kMaxCacheBufferSize);
+        if (mCacheBufferSize < newCacheBufferSize) {
+            uint8_t* newCache = new uint8_t[newCacheBufferSize];
+            memcpy(newCache, mCache.get(), mCachedSize);
+            mCache.reset(newCache);
+            mCacheBufferSize = newCacheBufferSize;
+
+            ALOGV("readAt: current cache window (%lld, %zu), new cache buffer size %zu",
+                    (long long) mCachedOffset, mCachedSize, mCacheBufferSize);
+        }
+    }
+    size_t bytesToRead = offset + size - mCachedOffset - mCachedSize;
+    size_t bytesRead = mStream->read(mCache.get() + mCachedSize, bytesToRead);
+    if (bytesRead > bytesToRead || bytesRead == 0) {
         // bytesRead is invalid
         mEOS = true;
-        return ERROR_END_OF_STREAM;
-    } if (bytesRead < size) {
-        // read some bytes but not all, set EOS and return ERROR_END_OF_STREAM next time
+        bytesRead = 0;
+    } else if (bytesRead < bytesToRead) {
+        // read some bytes but not all, set EOS
         mEOS = true;
     }
-    mReadPos += bytesRead;
-    return bytesRead;
+    mCachedSize += bytesRead;
+    ALOGV("readAt: current cache window (%lld, %zu)",
+            (long long) mCachedOffset, mCachedSize);
+
+    // here bytesAvailable could be negative if offset jumped past EOS.
+    int64_t bytesAvailable = mCachedOffset + mCachedSize - offset;
+    if (bytesAvailable <= 0) {
+        return ERROR_END_OF_STREAM;
+    }
+    if (bytesAvailable < (int64_t)size) {
+        size = bytesAvailable;
+    }
+    memcpy(mMemory->pointer(), mCache.get() + offset - mCachedOffset, size);
+    return size;
 }
 
 status_t HeifDataSource::getSize(off64_t* size) {
@@ -166,13 +269,15 @@
     // output color format should always be set via setOutputColor(), in case
     // it's not, default to HAL_PIXEL_FORMAT_RGB_565.
     mOutputColor(HAL_PIXEL_FORMAT_RGB_565),
-    mCurScanline(0) {
+    mCurScanline(0),
+    mFrameDecoded(false) {
 }
 
 HeifDecoderImpl::~HeifDecoderImpl() {
 }
 
 bool HeifDecoderImpl::init(HeifStream* stream, HeifFrameInfo* frameInfo) {
+    mFrameDecoded = false;
     sp<HeifDataSource> dataSource = new HeifDataSource(stream);
     if (!dataSource->init()) {
         return false;
@@ -256,6 +361,13 @@
 }
 
 bool HeifDecoderImpl::decode(HeifFrameInfo* frameInfo) {
+    // reset scanline pointer
+    mCurScanline = 0;
+
+    if (mFrameDecoded) {
+        return true;
+    }
+
     mFrameMemory = mRetriever->getFrameAtTime(0,
             IMediaSource::ReadOptions::SEEK_PREVIOUS_SYNC, mOutputColor);
     if (mFrameMemory == nullptr || mFrameMemory->pointer() == nullptr) {
@@ -264,6 +376,12 @@
     }
 
     VideoFrame* videoFrame = static_cast<VideoFrame*>(mFrameMemory->pointer());
+    if (videoFrame->mSize == 0 ||
+            mFrameMemory->size() < videoFrame->getFlattenedSize()) {
+        ALOGE("getFrameAtTime: videoFrame size is invalid");
+        return false;
+    }
+
     ALOGV("Decoded dimension %dx%d, display %dx%d, angle %d, rowbytes %d, size %d",
             videoFrame->mWidth,
             videoFrame->mHeight,
@@ -282,6 +400,7 @@
                 videoFrame->mIccSize,
                 videoFrame->getFlattenedIccData());
     }
+    mFrameDecoded = true;
     return true;
 }
 
diff --git a/media/libheif/HeifDecoderImpl.h b/media/libheif/HeifDecoderImpl.h
index 2f8f0f8..c2e4ff3 100644
--- a/media/libheif/HeifDecoderImpl.h
+++ b/media/libheif/HeifDecoderImpl.h
@@ -54,6 +54,7 @@
     sp<IMemory> mFrameMemory;
     android_pixel_format_t mOutputColor;
     size_t mCurScanline;
+    bool mFrameDecoded;
 };
 
 } // namespace android