components: VideoFramePool uses dedicated thread to fetch GraphicBlock

Originally VideoFramePool shared the same thread with V4L2Decoder. It
turned out the performance is pretty bad, many frames were dropped.

In this CL, VideoFramePool creates a dedicated thread and fetch
GraphicBlock on this thread.

Bug: 146406514
Test: pass e2e test

Change-Id: I3c51cf1ce1d8e40fb52ad947d779e8c0ad2f754c
diff --git a/components/V4L2DecodeComponent.cpp b/components/V4L2DecodeComponent.cpp
index 64cb4ba..4c657e5 100644
--- a/components/V4L2DecodeComponent.cpp
+++ b/components/V4L2DecodeComponent.cpp
@@ -275,8 +275,8 @@
         return;
     }
 
-    *pool = std::make_unique<VideoFramePool>(std::move(blockPool), size, pixelFormat, mIsSecure,
-                                             mDecoderTaskRunner);
+    *pool = VideoFramePool::Create(std::move(blockPool), size, pixelFormat, mIsSecure,
+                                   mDecoderTaskRunner);
 }
 
 c2_status_t V4L2DecodeComponent::stop() {
diff --git a/components/VideoFramePool.cpp b/components/VideoFramePool.cpp
index c2f68fc..acda536 100644
--- a/components/VideoFramePool.cpp
+++ b/components/VideoFramePool.cpp
@@ -12,6 +12,7 @@
 
 #include <android/hardware/graphics/common/1.0/types.h>
 #include <base/bind.h>
+#include <base/memory/ptr_util.h>
 #include <base/time/time.h>
 #include <log/log.h>
 
@@ -21,10 +22,21 @@
 
 namespace android {
 namespace {
+// The number of times and timeout used between subsequent calls when fetching graphic blocks.
 constexpr size_t kAllocateBufferMaxRetries = 32;
-constexpr size_t kFetchRetryDelayUs = 500;
+constexpr size_t kFetchRetryDelayUs = 1000;
 }  // namespace
 
+// static
+std::unique_ptr<VideoFramePool> VideoFramePool::Create(
+        std::shared_ptr<C2BlockPool> blockPool, const media::Size& size, HalPixelFormat pixelFormat,
+        bool isSecure, scoped_refptr<::base::SequencedTaskRunner> taskRunner) {
+    std::unique_ptr<VideoFramePool> pool = ::base::WrapUnique(new VideoFramePool(
+            std::move(blockPool), size, pixelFormat, isSecure, std::move(taskRunner)));
+    if (!pool->initialize()) return nullptr;
+    return pool;
+}
+
 VideoFramePool::VideoFramePool(std::shared_ptr<C2BlockPool> blockPool, const media::Size& size,
                                HalPixelFormat pixelFormat, bool isSecure,
                                scoped_refptr<::base::SequencedTaskRunner> taskRunner)
@@ -33,72 +45,110 @@
         mPixelFormat(pixelFormat),
         mMemoryUsage(isSecure ? C2MemoryUsage::READ_PROTECTED : C2MemoryUsage::CPU_READ,
                      static_cast<uint64_t>(BufferUsage::VIDEO_DECODER)),
-        mTaskRunner(std::move(taskRunner)) {
+        mClientTaskRunner(std::move(taskRunner)) {
     ALOGV("%s()", __func__);
-    ALOG_ASSERT(mTaskRunner->RunsTasksInCurrentSequence());
+    ALOG_ASSERT(mClientTaskRunner->RunsTasksInCurrentSequence());
     DCHECK(mBlockPool);
-    DCHECK(mTaskRunner);
+    DCHECK(mClientTaskRunner);
+}
 
-    mWeakThis = mWeakThisFactory.GetWeakPtr();
+bool VideoFramePool::initialize() {
+    if (!mFetchThread.Start()) {
+        ALOGE("Fetch thread failed to start.");
+        return false;
+    }
+    mFetchTaskRunner = mFetchThread.task_runner();
+
+    mClientWeakThis = mClientWeakThisFactory.GetWeakPtr();
+    mFetchWeakThis = mFetchWeakThisFactory.GetWeakPtr();
+
+    return true;
 }
 
 VideoFramePool::~VideoFramePool() {
     ALOGV("%s()", __func__);
-    ALOG_ASSERT(mTaskRunner->RunsTasksInCurrentSequence());
+    ALOG_ASSERT(mClientTaskRunner->RunsTasksInCurrentSequence());
 
-    mWeakThisFactory.InvalidateWeakPtrs();
+    mClientWeakThisFactory.InvalidateWeakPtrs();
+
+    if (mFetchThread.IsRunning()) {
+        mFetchTaskRunner->PostTask(FROM_HERE,
+                                   ::base::BindOnce(&VideoFramePool::destroyTask, mFetchWeakThis));
+        mFetchThread.Stop();
+    }
+}
+
+void VideoFramePool::destroyTask() {
+    ALOGV("%s()", __func__);
+    ALOG_ASSERT(mFetchTaskRunner->RunsTasksInCurrentSequence());
+
+    mFetchWeakThisFactory.InvalidateWeakPtrs();
 }
 
 void VideoFramePool::getVideoFrame(GetVideoFrameCB cb) {
     ALOGV("%s()", __func__);
-    ALOG_ASSERT(mTaskRunner->RunsTasksInCurrentSequence());
+    ALOG_ASSERT(mClientTaskRunner->RunsTasksInCurrentSequence());
 
-    bool isRunning = !mCbQueue.empty();
-    mCbQueue.push(std::move(cb));
-    if (!isRunning) tryFetchGraphicBlock();
+    ++mNumPendingRequests;
+    mFetchTaskRunner->PostTask(FROM_HERE, ::base::BindOnce(&VideoFramePool::getVideoFrameTask,
+                                                           mFetchWeakThis, std::move(cb)));
 }
 
 bool VideoFramePool::hasPendingRequests() const {
-    return !mCbQueue.empty();
+    ALOGV("%s()", __func__);
+    ALOG_ASSERT(mClientTaskRunner->RunsTasksInCurrentSequence());
+
+    return mNumPendingRequests > 0;
 }
 
-void VideoFramePool::tryFetchGraphicBlock() {
+void VideoFramePool::getVideoFrameTask(GetVideoFrameCB cb) {
     ALOGV("%s()", __func__);
-    ALOG_ASSERT(mTaskRunner->RunsTasksInCurrentSequence());
+    ALOG_ASSERT(mFetchTaskRunner->RunsTasksInCurrentSequence());
 
-    if (mCbQueue.empty()) return;
+    std::unique_ptr<VideoFrame> frame = nullptr;
 
-    std::shared_ptr<C2GraphicBlock> block;
-    auto err = mBlockPool->fetchGraphicBlock(mSize.width(), mSize.height(),
-                                             static_cast<uint32_t>(mPixelFormat), mMemoryUsage,
-                                             &block);
+    size_t numRetries = 0;
+    while (numRetries < kAllocateBufferMaxRetries) {
+        std::shared_ptr<C2GraphicBlock> block;
+        c2_status_t err = mBlockPool->fetchGraphicBlock(mSize.width(), mSize.height(),
+                                                        static_cast<uint32_t>(mPixelFormat),
+                                                        mMemoryUsage, &block);
 
-    if ((err == C2_TIMED_OUT || err == C2_BLOCKING) && mNumRetries++ < kAllocateBufferMaxRetries) {
-        ALOGD("fetchGraphicBlock() timeout. retry %zu times", mNumRetries);
-        mTaskRunner->PostDelayedTask(
-                FROM_HERE, ::base::BindOnce(&VideoFramePool::tryFetchGraphicBlock, mWeakThis),
-                ::base::TimeDelta::FromMicroseconds(kFetchRetryDelayUs));
-    } else if (err != C2_OK) {
-        ALOGE("Failed to fetch block, err=%d, retry %zu times", err, mNumRetries);
-        sendVideoFrame(nullptr);
-    } else {
-        mNumRetries = 0;
-        sendVideoFrame(VideoFrame::Create(std::move(block)));
-
-        if (!mCbQueue.empty()) {
-            mTaskRunner->PostTask(
-                    FROM_HERE, ::base::BindOnce(&VideoFramePool::tryFetchGraphicBlock, mWeakThis));
+        if (err == C2_OK) {
+            frame = VideoFrame::Create(std::move(block));
+            break;
+        } else if (err != C2_TIMED_OUT && err != C2_BLOCKING) {
+            ALOGE("Failed to fetch block, err=%d, retry %zu times", err, numRetries);
+            break;
+        } else {
+            ++numRetries;
+            ALOGD("fetchGraphicBlock() timeout. retry %zu times", numRetries);
+            usleep(kFetchRetryDelayUs);
         }
     }
+    if (numRetries == kAllocateBufferMaxRetries) {
+        ALOGE("Timeout to fetch block, retry %zu times", numRetries);
+    }
+
+    mClientTaskRunner->PostTask(
+            FROM_HERE, ::base::BindOnce(&VideoFramePool::onVideoFrameReady, mClientWeakThis,
+                                        std::move(cb), std::move(frame)));
 }
 
-void VideoFramePool::sendVideoFrame(std::unique_ptr<VideoFrame> frame) {
+void VideoFramePool::onVideoFrameReady(GetVideoFrameCB cb, std::unique_ptr<VideoFrame> frame) {
     ALOGV("%s()", __func__);
-    ALOG_ASSERT(mTaskRunner->RunsTasksInCurrentSequence());
-    ALOG_ASSERT(!mCbQueue.empty());
+    ALOG_ASSERT(mClientTaskRunner->RunsTasksInCurrentSequence());
 
-    auto cb = std::move(mCbQueue.front());
-    mCbQueue.pop();
+    --mNumPendingRequests;
+
+    if (!frame) {
+        ALOGE("Failed to get GraphicBlock, abandoning all pending requests.");
+        mClientWeakThisFactory.InvalidateWeakPtrs();
+        mClientWeakThis = mClientWeakThisFactory.GetWeakPtr();
+
+        mNumPendingRequests = 0;
+    }
+
     std::move(cb).Run(std::move(frame));
 }
 
diff --git a/components/include/v4l2_codec2/components/VideoFramePool.h b/components/include/v4l2_codec2/components/VideoFramePool.h
index 64e9d71..079e640 100644
--- a/components/include/v4l2_codec2/components/VideoFramePool.h
+++ b/components/include/v4l2_codec2/components/VideoFramePool.h
@@ -12,6 +12,7 @@
 #include <base/callback.h>
 #include <base/memory/weak_ptr.h>
 #include <base/sequenced_task_runner.h>
+#include <base/threading/thread.h>
 
 #include <size.h>
 #include <v4l2_codec2/components/VideoFrame.h>
@@ -26,14 +27,10 @@
 public:
     using GetVideoFrameCB = base::OnceCallback<void(std::unique_ptr<VideoFrame>)>;
 
-    // |blockPool| is the C2BlockPool that we fetch graphic blocks from.
-    // |size| is the resolution size of the required graphic blocks.
-    // |pixelFormat| is the pixel format of the required graphic blocks.
-    // |isSecure| indicates the video stream is encrypted or not.
-    // All public methods and the callbacks should be run on |taskRunner|.
-    VideoFramePool(std::shared_ptr<C2BlockPool> blockPool, const media::Size& size,
-                   HalPixelFormat pixelFormat, bool isSecure,
-                   scoped_refptr<::base::SequencedTaskRunner> taskRunner);
+    static std::unique_ptr<VideoFramePool> Create(
+            std::shared_ptr<C2BlockPool> blockPool, const media::Size& size,
+            HalPixelFormat pixelFormat, bool isSecure,
+            scoped_refptr<::base::SequencedTaskRunner> taskRunner);
     ~VideoFramePool();
 
     // Get a VideoFrame instance, which will be passed via |cb|.
@@ -44,20 +41,35 @@
     bool hasPendingRequests() const;
 
 private:
-    void tryFetchGraphicBlock();
-    void sendVideoFrame(std::unique_ptr<VideoFrame> frame);
+    // |blockPool| is the C2BlockPool that we fetch graphic blocks from.
+    // |size| is the resolution size of the required graphic blocks.
+    // |pixelFormat| is the pixel format of the required graphic blocks.
+    // |isSecure| indicates the video stream is encrypted or not.
+    // All public methods and the callbacks should be run on |taskRunner|.
+    VideoFramePool(std::shared_ptr<C2BlockPool> blockPool, const media::Size& size,
+                   HalPixelFormat pixelFormat, bool isSecure,
+                   scoped_refptr<::base::SequencedTaskRunner> taskRunner);
+    bool initialize();
+    void destroyTask();
+
+    void getVideoFrameTask(GetVideoFrameCB cb);
+    void onVideoFrameReady(GetVideoFrameCB cb, std::unique_ptr<VideoFrame> frame);
 
     std::shared_ptr<C2BlockPool> mBlockPool;
     const media::Size mSize;
     const HalPixelFormat mPixelFormat;
     const C2MemoryUsage mMemoryUsage;
 
-    std::queue<GetVideoFrameCB> mCbQueue;
-    size_t mNumRetries = 0;
+    size_t mNumPendingRequests = 0;
 
-    scoped_refptr<::base::SequencedTaskRunner> mTaskRunner;
-    base::WeakPtr<VideoFramePool> mWeakThis;
-    base::WeakPtrFactory<VideoFramePool> mWeakThisFactory{this};
+    scoped_refptr<::base::SequencedTaskRunner> mClientTaskRunner;
+    ::base::Thread mFetchThread{"VideoFramePoolFetchThread"};
+    scoped_refptr<::base::SequencedTaskRunner> mFetchTaskRunner;
+
+    ::base::WeakPtr<VideoFramePool> mClientWeakThis;
+    ::base::WeakPtr<VideoFramePool> mFetchWeakThis;
+    ::base::WeakPtrFactory<VideoFramePool> mClientWeakThisFactory{this};
+    ::base::WeakPtrFactory<VideoFramePool> mFetchWeakThisFactory{this};
 };
 
 }  // namespace android