codec2: use temporal hack for buffer recycling in byte-buffer mode

This is not a proper solution!! Since we don't have one to recycle output
buffers in byte-buffer mode (no IGBP is given), we temporarily use an internal
mock queue for buffers outputted to client and recycle it when it is popped
from the queue by a new push.

Bug: 79239042
Test: CtsMediaTestCases android.media.cts.DecoderTest#testCodecResetsVP8WithoutSurface

Change-Id: I7b6778a3fbacc61eb0c1b9c741a749df3b6689d9
diff --git a/C2VDAComponent.cpp b/C2VDAComponent.cpp
index 1f15e2d..80e2e81 100644
--- a/C2VDAComponent.cpp
+++ b/C2VDAComponent.cpp
@@ -58,6 +58,13 @@
 const uint32_t kDpbOutputBufferExtraCount = 3;  // Use the same number as ACodec.
 const int kDequeueRetryDelayUs = 10000;  // Wait time of dequeue buffer retry in microseconds.
 
+// Hack(b/79239042): Max size of mMockBufferQueueInClient.
+// This value is empirically picked from previous CTS try-run. If this value is too big, it may
+// cause VDA deadlock when it requires more buffers to decode and dequeue a new one. On the other
+// hand, too small value may produce wrong display picture because recycling goes faster than
+// rendering.
+const size_t kMockMaxBuffersInClient = 5;
+
 }  // namespace
 
 C2VDAComponent::IntfImpl::IntfImpl(C2String name, const std::shared_ptr<C2ReflectorHelper>& helper)
@@ -140,7 +147,7 @@
                          .build());
 
     C2Allocator::id_t inputAllocators[] = {C2PlatformAllocatorStore::ION};
-    C2Allocator::id_t outputAllocators[] = {C2PlatformAllocatorStore::GRALLOC};
+    C2Allocator::id_t outputAllocators[] = {C2VDAAllocatorStore::V4L2_BUFFERQUEUE};
 
     addParameter(
             DefineParam(mInputAllocatorIds, C2_PARAMKEY_INPUT_ALLOCATORS)
@@ -203,6 +210,7 @@
         mComponentState(ComponentState::UNINITIALIZED),
         mDrainWithEOS(false),
         mLastOutputTimestamp(-1),
+        mSurfaceMode(true),
         mCodecProfile(media::VIDEO_CODEC_PROFILE_UNKNOWN),
         mState(State::UNLOADED),
         mWeakThisFactory(this) {
@@ -391,7 +399,18 @@
     CHECK_EQ(info->mState, GraphicBlockInfo::State::OWNED_BY_ACCELERATOR);
     // Output buffer will be passed to client soon along with mListener->onWorkDone_nb().
     info->mState = GraphicBlockInfo::State::OWNED_BY_CLIENT;
-    mBuffersInClient++;
+    if (mSurfaceMode) {
+        mBuffersInClient++;
+    } else {  // byte-buffer mode
+        // Hack(b/79239042)
+        mMockBufferQueueInClient.push_back(info->mSlotId);
+        if (mMockBufferQueueInClient.size() > kMockMaxBuffersInClient) {
+            mTaskRunner->PostTask(FROM_HERE, ::base::Bind(&C2VDAComponent::onOutputBufferReturned,
+                                                          ::base::Unretained(this),
+                                                          mMockBufferQueueInClient.front()));
+            mMockBufferQueueInClient.pop_front();
+        }
+    }
 
     // Attach output buffer to the work corresponded to bitstreamId.
     auto block = info->mGraphicBlock;
@@ -543,6 +562,7 @@
 
     mGraphicBlocks.clear();
 
+    mMockBufferQueueInClient.clear();  // Hack(b/79239042)
     stopDequeueThread();
 
     mStopDoneEvent->Signal();
@@ -690,23 +710,32 @@
         }
     }
 
+    mMockBufferQueueInClient.clear();  // Hack(b/79239042)
     stopDequeueThread();
     mGraphicBlocks.clear();
 
-    // Set requested buffer count to C2VdaBqBlockPool.
-    std::shared_ptr<C2VdaBqBlockPool> bqPool =
-            std::static_pointer_cast<C2VdaBqBlockPool>(mOutputBlockPool);
-    if (bqPool) {
-        err = bqPool->requestNewBufferSet(static_cast<uint32_t>(bufferCount));
-        if (err != C2_OK) {
-            ALOGE("failed to set buffer count magic to block pool: %d", err);
-            reportError(err);
-            return err;
+    if (mOutputBlockPool->getAllocatorId() == C2PlatformAllocatorStore::BUFFERQUEUE) {
+        // Set requested buffer count to C2VdaBqBlockPool.
+        std::shared_ptr<C2VdaBqBlockPool> bqPool =
+                std::static_pointer_cast<C2VdaBqBlockPool>(mOutputBlockPool);
+        if (bqPool) {
+            err = bqPool->requestNewBufferSet(static_cast<int32_t>(bufferCount));
+            if (err == C2_NO_INIT) {
+                ALOGD("No surface in block pool, output is byte-buffer mode...");
+                mSurfaceMode = false;
+            } else if (err != C2_OK) {
+                ALOGE("failed to set buffer count magic to block pool: %d", err);
+                reportError(err);
+                return err;
+            }
+        } else {
+            ALOGE("static_pointer_cast C2VdaBqBlockPool failed...");
+            reportError(C2_CORRUPTED);
+            return C2_CORRUPTED;
         }
-    } else {
-        ALOGE("Component only supports C2VdaBqBlockPool");
-        reportError(C2_CORRUPTED);
-        return C2_CORRUPTED;
+    } else {  // CCodec falls back to use C2BasicGraphicBlockPool
+        ALOGD("CCodec falls back to use C2BasicGraphicBlockPool...");
+        mSurfaceMode = false;
     }
 
     for (size_t i = 0; i < bufferCount; ++i) {
@@ -724,7 +753,7 @@
     }
     mOutputFormat.mMinNumBuffers = bufferCount;
 
-    if (!startDequeueThread(size, pixelFormat)) {
+    if (mSurfaceMode && !startDequeueThread(size, pixelFormat)) {
         reportError(C2_CORRUPTED);
         return C2_CORRUPTED;
     }
@@ -795,7 +824,11 @@
     info.mHandle = std::move(passedHandle);
     info.mPlanes = std::move(passedPlanes);
 
-    info.mSlotId = getSlotFromGraphicBlockHandle(info.mGraphicBlock->handle());
+    if (mSurfaceMode) {
+        info.mSlotId = getSlotFromGraphicBlockHandle(info.mGraphicBlock->handle());
+    } else {  // byte-buffer mode
+        info.mSlotId = static_cast<uint32_t>(info.mBlockId);
+    }
 
     mGraphicBlocks.push_back(std::move(info));
 }
diff --git a/include/C2VDAComponent.h b/include/C2VDAComponent.h
index 53fcf4e..ed711c9 100644
--- a/include/C2VDAComponent.h
+++ b/include/C2VDAComponent.h
@@ -284,6 +284,13 @@
     int64_t mLastOutputTimestamp;
     // The pointer of output block pool.
     std::shared_ptr<C2BlockPool> mOutputBlockPool;
+    // Hack(b/79239042): We do not have a solution to recycle buffers in byte-buffer mode now. This
+    // is a fake buffer queue to record buffers outputted to client, and regard buffer is returned
+    // when it is popped by a new push of the queue (size: kMockMaxBuffersInClient).
+    // TODO: provide proper solution and get rid of this hack.
+    std::list<uint32_t> mMockBufferQueueInClient;
+    // The indicator of whether output has surface.
+    bool mSurfaceMode;
 
     // The following members should be utilized on parent thread.
 
diff --git a/tests/C2VDACompIntf_test.cpp b/tests/C2VDACompIntf_test.cpp
index 6ed0753..aea52b2 100644
--- a/tests/C2VDACompIntf_test.cpp
+++ b/tests/C2VDACompIntf_test.cpp
@@ -30,7 +30,7 @@
 const char* MEDIA_MIMETYPE_VIDEO_AVC = "video/avc";
 
 const C2Allocator::id_t kInputAllocators[] = {C2PlatformAllocatorStore::ION};
-const C2Allocator::id_t kOutputAllocators[] = {C2PlatformAllocatorStore::GRALLOC};
+const C2Allocator::id_t kOutputAllocators[] = {C2PlatformAllocatorStore::V4L2_BUFFERQUEUE};
 const C2BlockPool::local_id_t kDefaultOutputBlockPool = C2BlockPool::BASIC_GRAPHIC;
 
 class C2VDACompIntfTest : public ::testing::Test {