heif: cache the entire stream if possible to support seek
Also, reuse decoding result if decode is called again. Currently
decode only retrieves primary picture, so there is no point to
go to metadata retriever again.
Bug: 64077740
Change-Id: I9243de396957f4d717c386bbaa8692494d624998
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