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