IMediaSource: Improve shared memory buffer transfer

Bug: 29125703
Change-Id: Icf1180dee65f6504e6c10dd4d5b28a8e441f67d1
diff --git a/include/media/IMediaSource.h b/include/media/IMediaSource.h
index 524e7aa..7a7599d 100644
--- a/include/media/IMediaSource.h
+++ b/include/media/IMediaSource.h
@@ -18,14 +18,17 @@
 
 #define IMEDIA_SOURCE_BASE_H_
 
+#include <map>
+
 #include <binder/IInterface.h>
+#include <binder/IMemory.h>
+#include <media/stagefright/MediaBuffer.h>
 #include <media/stagefright/MediaErrors.h>
 
 namespace android {
 
 struct MediaSource;
 class MetaData;
-class MediaBuffer;
 class MediaBufferGroup;
 
 class IMediaSource : public IInterface {
@@ -56,7 +59,7 @@
     // a) not request a seek
     // b) not be late, i.e. lateness_us = 0
     struct ReadOptions {
-        enum SeekMode {
+        enum SeekMode : int32_t {
             SEEK_PREVIOUS_SYNC,
             SEEK_NEXT_SYNC,
             SEEK_CLOSEST_SYNC,
@@ -72,6 +75,7 @@
         void clearSeekTo();
         bool getSeekTo(int64_t *time_us, SeekMode *mode) const;
 
+        // TODO: remove this if unused.
         void setLateBy(int64_t lateness_us);
         int64_t getLateBy() const;
 
@@ -79,6 +83,11 @@
         void clearNonBlocking();
         bool getNonBlocking() const;
 
+        // Used to clear all non-persistent options for multiple buffer reads.
+        void clearNonPersistent() {
+            clearSeekTo();
+        }
+
     private:
         enum Options {
             kSeekTo_Option      = 1,
@@ -98,21 +107,26 @@
     // A result of INFO_FORMAT_CHANGED indicates that the format of this
     // MediaSource has changed mid-stream, the client can continue reading
     // but should be prepared for buffers of the new configuration.
+    //
+    // TODO: consider removing read() in favor of readMultiple().
     virtual status_t read(
             MediaBuffer **buffer, const ReadOptions *options = NULL) = 0;
 
-    // Returns a vector of new buffers of data. The vector size could be
-    // <= |maxNumBuffers|. Used for buffers with small size
-    // since all buffer data are passed back by binder, not shared memory.
+    // Returns a vector of new buffers of data, where the new buffers are added
+    // to the end of the vector.
     // Call blocks until an error is encountered, or the end of the stream is
     // reached, or format change is hit, or |kMaxNumReadMultiple| buffers have
     // been read.
-    // End of stream is signalled by a result of ERROR_END_OF_STREAM.
+    // End of stream is signaled by a result of ERROR_END_OF_STREAM.
     // A result of INFO_FORMAT_CHANGED indicates that the format of this
     // MediaSource has changed mid-stream, the client can continue reading
     // but should be prepared for buffers of the new configuration.
+    //
+    // ReadOptions may be specified. Persistent options apply to all reads;
+    // non-persistent options (e.g. seek) apply only to the first read.
     virtual status_t readMultiple(
-            Vector<MediaBuffer *> *buffers, uint32_t maxNumBuffers = 1) = 0;
+            Vector<MediaBuffer *> *buffers, uint32_t maxNumBuffers = 1,
+            const ReadOptions *options = nullptr) = 0;
 
     // Returns true if |readMultiple| is supported, otherwise false.
     virtual bool supportReadMultiple() = 0;
@@ -148,20 +162,92 @@
     }
 
     virtual status_t readMultiple(
-            Vector<MediaBuffer *> * /* buffers */, uint32_t /* maxNumBuffers = 1 */) {
+            Vector<MediaBuffer *> * /* buffers */, uint32_t /* maxNumBuffers = 1 */,
+            const ReadOptions * /* options = nullptr */) {
         return ERROR_UNSUPPORTED;
     }
 
     virtual bool supportReadMultiple() {
         return false;
     }
+
+    static const size_t kBinderMediaBuffers = 4; // buffers managed by BnMediaSource
+
 protected:
     virtual ~BnMediaSource();
 
 private:
-    MediaBufferGroup *mGroup;
-};
+    uint32_t mBuffersSinceStop; // Buffer tracking variable
 
+    std::unique_ptr<MediaBufferGroup> mGroup;
+
+    // To prevent marshalling IMemory with each read transaction, we cache the IMemory pointer
+    // into a map.
+    //
+    // This is converted into an index, which is used to identify the associated memory
+    // on the receiving side.  We hold a reference to the IMemory here to ensure it doesn't
+    // change underneath us.
+
+    struct IndexCache {
+        IndexCache() : mIndex(0) { }
+
+        // Returns the index of the IMemory stored in cache or 0 if not found.
+        uint64_t lookup(const sp<IMemory> &mem) {
+            auto p = mMemoryToIndex.find(mem.get());
+            if (p == mMemoryToIndex.end()) {
+                return 0;
+            }
+            if (MediaBuffer::isDeadObject(p->second.first)) {
+                // this object's dead
+                ALOGW("Attempting to lookup a dead IMemory");
+                (void)mMemoryToIndex.erase(p);
+                return 0;
+            }
+            ALOGW_IF(p->second.first.get() != mem.get(), "Mismatched buffers without reset");
+            return p->second.second;
+        }
+
+        // Returns the index of the IMemory stored in the index cache.
+        uint64_t insert(const sp<IMemory> &mem) {
+            auto p = mMemoryToIndex.find(mem.get());
+            if (p == mMemoryToIndex.end()) {
+                if (mIndex == UINT64_MAX) {
+                    ALOGE("Index overflow");
+                    mIndex = 1; // skip overflow condition and hope for the best
+                } else {
+                    ++mIndex;
+                }
+                (void)mMemoryToIndex.emplace(// C++11 mem.get(), std::make_pair(mem, mIndex))
+                        std::piecewise_construct,
+                        std::forward_as_tuple(mem.get()), std::forward_as_tuple(mem, mIndex));
+                return mIndex;
+            }
+            ALOGW("IMemory already inserted into cache");
+            return p->second.second;
+        }
+
+        void reset() {
+            mMemoryToIndex.clear();
+            mIndex = 0;
+        }
+
+        void gc() {
+            for (auto it = mMemoryToIndex.begin(); it != mMemoryToIndex.end(); ) {
+                if (MediaBuffer::isDeadObject(it->second.first)) {
+                    it = mMemoryToIndex.erase(it);
+                } else {
+                    ++it;
+                }
+            }
+        }
+
+    private:
+        uint64_t mIndex;
+        // C++14 unordered_map erase on iterator is stable; C++11 has no guarantee.
+        // Could key on uintptr_t instead of IMemory *
+        std::map<IMemory *, std::pair<sp<IMemory>, uint64_t>> mMemoryToIndex;
+    } mIndexCache;
+};
 
 }  // namespace android
 
diff --git a/include/media/stagefright/MediaBuffer.h b/include/media/stagefright/MediaBuffer.h
index 18b80e3..55b1f58 100644
--- a/include/media/stagefright/MediaBuffer.h
+++ b/include/media/stagefright/MediaBuffer.h
@@ -18,6 +18,8 @@
 
 #define MEDIA_BUFFER_H_
 
+#include <atomic>
+#include <list>
 #include <media/stagefright/foundation/MediaBufferBase.h>
 
 #include <pthread.h>
@@ -60,6 +62,12 @@
 
     MediaBuffer(const sp<ABuffer> &buffer);
 
+    MediaBuffer(const sp<IMemory> &mem) :
+        MediaBuffer((uint8_t *)mem->pointer() + sizeof(SharedControl), mem->size()) {
+        // delegate and override mMemory
+        mMemory = mem;
+    }
+
     // Decrements the reference count and returns the buffer to its
     // associated MediaBufferGroup if the reference count drops to 0.
     virtual void release();
@@ -91,9 +99,44 @@
 
     int refcount() const;
 
+    bool isDeadObject() const {
+        return isDeadObject(mMemory);
+    }
+
+    static bool isDeadObject(const sp<IMemory> &memory) {
+        if (memory.get() == nullptr || memory->pointer() == nullptr) return false;
+        return reinterpret_cast<SharedControl *>(memory->pointer())->isDeadObject();
+    }
+
 protected:
+    // MediaBuffer remote releases are handled through a
+    // pending release count variable stored in a SharedControl block
+    // at the start of the IMemory.
+
+    // Returns old value of pending release count.
+    inline int32_t addPendingRelease(int32_t value) {
+        return getSharedControl()->addPendingRelease(value);
+    }
+
+    // Issues all pending releases (works in parallel).
+    // Assumes there is a MediaBufferObserver.
+    inline void resolvePendingRelease() {
+        if (mMemory.get() == nullptr) return;
+        while (addPendingRelease(-1) > 0) {
+            release();
+        }
+        addPendingRelease(1);
+    }
+
+    // true if MediaBuffer is observed (part of a MediaBufferGroup).
+    inline bool isObserved() const {
+        return mObserver != nullptr;
+    }
+
     virtual ~MediaBuffer();
 
+    sp<IMemory> mMemory;
+
 private:
     friend class MediaBufferGroup;
     friend class OMXDecoder;
@@ -105,7 +148,6 @@
     void claim();
 
     MediaBufferObserver *mObserver;
-    MediaBuffer *mNextBuffer;
     int mRefCount;
 
     void *mData;
@@ -119,12 +161,57 @@
 
     MediaBuffer *mOriginal;
 
-    void setNextBuffer(MediaBuffer *buffer);
-    MediaBuffer *nextBuffer();
-
     MediaBuffer(const MediaBuffer &);
     MediaBuffer &operator=(const MediaBuffer &);
-    sp<IMemory> mMemory;
+
+    // SharedControl block at the start of IMemory.
+    struct SharedControl {
+        enum {
+            FLAG_DEAD_OBJECT = (1 << 0),
+        };
+
+        // returns old value
+        inline int32_t addPendingRelease(int32_t value) {
+            return std::atomic_fetch_add_explicit(
+                    &mPendingRelease, (int_least32_t)value, std::memory_order_seq_cst);
+        }
+
+        inline int32_t getPendingRelease() const {
+            return std::atomic_load_explicit(&mPendingRelease, std::memory_order_seq_cst);
+        }
+
+        inline void setPendingRelease(int32_t value) {
+            std::atomic_store_explicit(
+                    &mPendingRelease, (int_least32_t)value, std::memory_order_seq_cst);
+        }
+
+        inline bool isDeadObject() const {
+            return (std::atomic_load_explicit(
+                    &mFlags, std::memory_order_seq_cst) & FLAG_DEAD_OBJECT) != 0;
+        }
+
+        inline void setDeadObject() {
+            (void)std::atomic_fetch_or_explicit(
+                    &mFlags, (int_least32_t)FLAG_DEAD_OBJECT, std::memory_order_seq_cst);
+        }
+
+        inline void clear() {
+            std::atomic_store_explicit(
+                    &mFlags, (int_least32_t)0, std::memory_order_seq_cst);
+            std::atomic_store_explicit(
+                    &mPendingRelease, (int_least32_t)0, std::memory_order_seq_cst);
+        }
+
+    private:
+        // Caution: atomic_int_fast32_t is 64 bits on LP64.
+        std::atomic_int_least32_t mFlags;
+        std::atomic_int_least32_t mPendingRelease;
+        int32_t unused[6]; // additional buffer space
+    };
+
+    inline SharedControl *getSharedControl() const {
+         return reinterpret_cast<SharedControl *>(mMemory->pointer());
+     }
 };
 
 }  // namespace android
diff --git a/include/media/stagefright/MediaBufferGroup.h b/include/media/stagefright/MediaBufferGroup.h
index 7ca3fa1..d1f59f4 100644
--- a/include/media/stagefright/MediaBufferGroup.h
+++ b/include/media/stagefright/MediaBufferGroup.h
@@ -29,7 +29,7 @@
 
 class MediaBufferGroup : public MediaBufferObserver {
 public:
-    MediaBufferGroup();
+    MediaBufferGroup(size_t growthLimit = 0);
     ~MediaBufferGroup();
 
     void add_buffer(MediaBuffer *buffer);
@@ -45,6 +45,11 @@
     status_t acquire_buffer(
             MediaBuffer **buffer, bool nonBlocking = false, size_t requestedSize = 0);
 
+    size_t buffers() const { return mBuffers.size(); }
+
+    // freeBuffers is the number of free buffers allowed to remain.
+    void gc(size_t freeBuffers = 0);
+
 protected:
     virtual void signalBufferReturned(MediaBuffer *buffer);
 
@@ -53,8 +58,8 @@
 
     Mutex mLock;
     Condition mCondition;
-
-    MediaBuffer *mFirstBuffer, *mLastBuffer;
+    size_t mGrowthLimit;  // Do not automatically grow group larger than this.
+    std::list<MediaBuffer *> mBuffers;
 
     MediaBufferGroup(const MediaBufferGroup &);
     MediaBufferGroup &operator=(const MediaBufferGroup &);