codec2: handle CCodec sends empty buffer at EOS

After ag/4278466, CCodec changes to send EOS work with no input buffer rather
than a dummy buffer. Handle this case for C2VDAComponent.

Bug: 73261432
Test: CtsMediaTestCases
android.media.cts.MediaCodecTest
android.media.cts.DecoderTest#testDecodeWithEOSOnLastBuffer
android.media.cts.MediaPlayerTest#testLocalVideo_MP4_H264_480x360_1000kbps_25fps_AAC_Stereo_128kbps_44110Hz

Change-Id: Ib43f39804fc63520c4320f0a4a76822c8b15e8f0
diff --git a/C2VDAComponent.cpp b/C2VDAComponent.cpp
index a6698cf..5eec1ef 100644
--- a/C2VDAComponent.cpp
+++ b/C2VDAComponent.cpp
@@ -307,10 +307,15 @@
     auto drainMode = mQueue.front().mDrainMode;
     mQueue.pop();
 
-    CHECK_EQ(work->input.buffers.size(), 1u);
-    C2ConstLinearBlock linearBlock = work->input.buffers.front()->data().linearBlocks().front();
-    // linearBlock.size() == 0 means this is a dummy work. No decode needed.
-    if (linearBlock.size() > 0) {
+    CHECK_LE(work->input.buffers.size(), 1u);
+    if (work->input.buffers.empty()) {
+        // Client may queue an EOS work with no input buffer, otherwise every work must have one
+        // input buffer.
+        CHECK(drainMode != NO_DRAIN);
+    } else {
+        // If input.buffers is not empty, the buffer should have meaningful content inside.
+        C2ConstLinearBlock linearBlock = work->input.buffers.front()->data().linearBlocks().front();
+        CHECK_GT(linearBlock.size(), 0u);
         // Send input buffer to VDA for decode.
         // Use frameIndex as bitstreamId.
         int32_t bitstreamId = frameIndexToBitstreamId(work->input.ordinal.frameIndex);
@@ -1072,10 +1077,12 @@
 }
 
 bool C2VDAComponent::isWorkDone(const C2Work* work) const {
+    if (work->input.buffers.empty()) {
+        // This is EOS work with no input buffer and should be processed by reportEOSWork().
+        return false;
+    }
     if (work->input.buffers.front()) {
         // Input buffer is still owned by VDA.
-        // This condition could also recognize dummy EOS work since it won't get
-        // onInputBufferDone(), input buffer won't be reset until reportEOSWork().
         return false;
     }
     if (mPendingOutputEOS && mPendingWorks.size() == 1u) {
@@ -1106,7 +1113,9 @@
 
     std::unique_ptr<C2Work> eosWork(std::move(mPendingWorks.front()));
     mPendingWorks.pop_front();
-    eosWork->input.buffers.front().reset();
+    if (!eosWork->input.buffers.empty()) {
+        eosWork->input.buffers.front().reset();
+    }
     eosWork->result = C2_OK;
     eosWork->workletsProcessed = static_cast<uint32_t>(eosWork->worklets.size());
     eosWork->worklets.front()->output.flags = C2FrameData::FLAG_END_OF_STREAM;
@@ -1126,16 +1135,20 @@
 
         // TODO: correlate the definition of flushed work result to framework.
         work->result = C2_NOT_FOUND;
-        // When the work is abandoned, the input.buffers.front() shall reset by component.
-        work->input.buffers.front().reset();
+        // When the work is abandoned, buffer in input.buffers shall reset by component.
+        if (!work->input.buffers.empty()) {
+            work->input.buffers.front().reset();
+        }
         abandonedWorks.emplace_back(std::move(work));
     }
 
     for (auto& work : mAbandonedWorks) {
         // TODO: correlate the definition of flushed work result to framework.
         work->result = C2_NOT_FOUND;
-        // When the work is abandoned, the input.buffers.front() shall reset by component.
-        work->input.buffers.front().reset();
+        // When the work is abandoned, buffer in input.buffers shall reset by component.
+        if (!work->input.buffers.empty()) {
+            work->input.buffers.front().reset();
+        }
         abandonedWorks.emplace_back(std::move(work));
     }
     mAbandonedWorks.clear();