diff options
Diffstat (limited to 'C2VDAComponent.cpp')
-rw-r--r-- | C2VDAComponent.cpp | 369 |
1 files changed, 258 insertions, 111 deletions
diff --git a/C2VDAComponent.cpp b/C2VDAComponent.cpp index 2b3abde..5eec1ef 100644 --- a/C2VDAComponent.cpp +++ b/C2VDAComponent.cpp @@ -12,11 +12,14 @@ #endif #define __C2_GENERATE_GLOBAL_VARS__ +#include <C2VDAAllocatorStore.h> +#include <C2VdaBqBlockPool.h> #include <C2VDAComponent.h> #include <C2VDASupport.h> // to getParamReflector from vda store #include <videodev2.h> +#include <C2AllocatorGralloc.h> #include <C2ComponentFactory.h> #include <C2PlatformSupport.h> @@ -52,6 +55,16 @@ const C2String kH264DecoderName = "c2.vda.avc.decoder"; const C2String kVP8DecoderName = "c2.vda.vp8.decoder"; const C2String kVP9DecoderName = "c2.vda.vp9.decoder"; +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) @@ -134,7 +147,7 @@ C2VDAComponent::IntfImpl::IntfImpl(C2String name, const std::shared_ptr<C2Reflec .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) @@ -170,28 +183,6 @@ C2VDAComponent::IntfImpl::IntfImpl(C2String name, const std::shared_ptr<C2Reflec CHECK_NE(mComponentState, ComponentState::UNINITIALIZED); \ } while (0) -class C2VDAGraphicBuffer : public C2Buffer { -public: - C2VDAGraphicBuffer(const std::shared_ptr<C2GraphicBlock>& block, const media::Rect& visibleRect, - const base::Closure& releaseCB); - ~C2VDAGraphicBuffer() override; - -private: - base::Closure mReleaseCB; -}; - -C2VDAGraphicBuffer::C2VDAGraphicBuffer(const std::shared_ptr<C2GraphicBlock>& block, - const media::Rect& visibleRect, - const base::Closure& releaseCB) - : C2Buffer({block->share(C2Rect(visibleRect.width(), visibleRect.height()), C2Fence())}), - mReleaseCB(releaseCB) {} - -C2VDAGraphicBuffer::~C2VDAGraphicBuffer() { - if (!mReleaseCB.is_null()) { - mReleaseCB.Run(); - } -} - C2VDAComponent::VideoFormat::VideoFormat(HalPixelFormat pixelFormat, uint32_t minNumBuffers, media::Size codedSize, media::Rect visibleRect) : mPixelFormat(pixelFormat), @@ -199,15 +190,27 @@ C2VDAComponent::VideoFormat::VideoFormat(HalPixelFormat pixelFormat, uint32_t mi mCodedSize(codedSize), mVisibleRect(visibleRect) {} +static uint32_t getSlotFromGraphicBlockHandle(const C2Handle* const handle) { + uint32_t width, height, format, stride, igbp_slot, generation; + uint64_t usage, igbp_id; + _UnwrapNativeCodec2GrallocMetadata( + handle, &width, &height, &format, &usage, &stride, &generation, &igbp_id, &igbp_slot); + ALOGV("Unwrap Metadata: igbp[%" PRIu64 ", %u] (%u*%u, fmt %#x, usage %" PRIx64 ", stride %u)", + igbp_id, igbp_slot, width, height, format, usage, stride); + return igbp_slot; +} + C2VDAComponent::C2VDAComponent(C2String name, c2_node_id_t id, const std::shared_ptr<C2ReflectorHelper>& helper) : mIntfImpl(std::make_shared<IntfImpl>(name, helper)), mIntf(std::make_shared<SimpleInterface<IntfImpl>>(name.c_str(), id, mIntfImpl)), mThread("C2VDAComponentThread"), + mDequeueThread("C2VDAComponentDequeueThread"), mVDAInitResult(VideoDecodeAcceleratorAdaptor::Result::ILLEGAL_STATE), mComponentState(ComponentState::UNINITIALIZED), - mDrainWithEOS(false), + mPendingOutputEOS(false), mLastOutputTimestamp(-1), + mSurfaceMode(true), mCodecProfile(media::VIDEO_CODEC_PROFILE_UNKNOWN), mState(State::UNLOADED), mWeakThisFactory(this) { @@ -229,7 +232,7 @@ C2VDAComponent::~C2VDAComponent() { if (mThread.IsRunning()) { mTaskRunner->PostTask(FROM_HERE, - base::Bind(&C2VDAComponent::onDestroy, base::Unretained(this))); + ::base::Bind(&C2VDAComponent::onDestroy, ::base::Unretained(this))); mThread.Stop(); } } @@ -241,9 +244,10 @@ void C2VDAComponent::onDestroy() { mVDAAdaptor->destroy(); mVDAAdaptor.reset(nullptr); } + stopDequeueThread(); } -void C2VDAComponent::onStart(media::VideoCodecProfile profile, base::WaitableEvent* done) { +void C2VDAComponent::onStart(media::VideoCodecProfile profile, ::base::WaitableEvent* done) { DCHECK(mTaskRunner->BelongsToCurrentThread()); ALOGV("onStart"); CHECK_EQ(mComponentState, ComponentState::UNINITIALIZED); @@ -269,8 +273,6 @@ void C2VDAComponent::onQueueWork(std::unique_ptr<C2Work> work) { ALOGV("onQueueWork: flags=0x%x, index=%llu, timestamp=%llu", work->input.flags, work->input.ordinal.frameIndex.peekull(), work->input.ordinal.timestamp.peekull()); EXPECT_RUNNING_OR_RETURN_ON_ERROR(); - // It is illegal for client to put new works while component is still flushing. - CHECK_NE(mComponentState, ComponentState::FLUSHING); uint32_t drainMode = NO_DRAIN; if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) { @@ -280,7 +282,7 @@ void C2VDAComponent::onQueueWork(std::unique_ptr<C2Work> work) { // TODO(johnylin): set a maximum size of mQueue and check if mQueue is already full. mTaskRunner->PostTask(FROM_HERE, - base::Bind(&C2VDAComponent::onDequeueWork, base::Unretained(this))); + ::base::Bind(&C2VDAComponent::onDequeueWork, ::base::Unretained(this))); } void C2VDAComponent::onDequeueWork() { @@ -290,8 +292,9 @@ void C2VDAComponent::onDequeueWork() { if (mQueue.empty()) { return; } - if (mComponentState == ComponentState::DRAINING) { - ALOGV("Temporarily stop dequeueing works since component is draining."); + if (mComponentState == ComponentState::DRAINING || + mComponentState == ComponentState::FLUSHING) { + ALOGV("Temporarily stop dequeueing works since component is draining/flushing."); return; } if (mComponentState != ComponentState::STARTED) { @@ -304,10 +307,15 @@ void C2VDAComponent::onDequeueWork() { 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); @@ -322,15 +330,15 @@ void C2VDAComponent::onDequeueWork() { if (drainMode != NO_DRAIN) { mVDAAdaptor->flush(); mComponentState = ComponentState::DRAINING; - mDrainWithEOS = drainMode == DRAIN_COMPONENT_WITH_EOS; + mPendingOutputEOS = drainMode == DRAIN_COMPONENT_WITH_EOS; } // Put work to mPendingWorks. mPendingWorks.emplace_back(std::move(work)); if (!mQueue.empty()) { - mTaskRunner->PostTask(FROM_HERE, - base::Bind(&C2VDAComponent::onDequeueWork, base::Unretained(this))); + mTaskRunner->PostTask(FROM_HERE, ::base::Bind(&C2VDAComponent::onDequeueWork, + ::base::Unretained(this))); } } @@ -351,16 +359,9 @@ void C2VDAComponent::onInputBufferDone(int32_t bitstreamId) { reportFinishedWorkIfAny(); } -// This is used as callback while output buffer is released by client. -// TODO(johnylin): consider to use C2Buffer::registerOnDestroyNotify instead -void C2VDAComponent::returnOutputBuffer(int32_t pictureBufferId) { - mTaskRunner->PostTask(FROM_HERE, base::Bind(&C2VDAComponent::onOutputBufferReturned, - base::Unretained(this), pictureBufferId)); -} - -void C2VDAComponent::onOutputBufferReturned(int32_t pictureBufferId) { +void C2VDAComponent::onOutputBufferReturned(uint32_t slotId) { DCHECK(mTaskRunner->BelongsToCurrentThread()); - ALOGV("onOutputBufferReturned: picture id=%d", pictureBufferId); + ALOGV("onOutputBufferReturned: slot id=%u", slotId); if (mComponentState == ComponentState::UNINITIALIZED) { // Output buffer is returned from client after component is stopped. Just let the buffer be // released. @@ -369,7 +370,7 @@ void C2VDAComponent::onOutputBufferReturned(int32_t pictureBufferId) { // TODO(johnylin): when buffer is returned, we should confirm that output format is not changed // yet. If changed, just let the buffer be released. - GraphicBlockInfo* info = getGraphicBlockById(pictureBufferId); + GraphicBlockInfo* info = getGraphicBlockBySlot(slotId); if (!info) { reportError(C2_CORRUPTED); return; @@ -402,15 +403,28 @@ void C2VDAComponent::onOutputBufferDone(int32_t pictureBufferId, int32_t bitstre 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; + 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. - work->worklets.front()->output.buffers.emplace_back(std::make_shared<C2VDAGraphicBuffer>( - info->mGraphicBlock, mOutputFormat.mVisibleRect, - base::Bind(&C2VDAComponent::returnOutputBuffer, mWeakThisFactory.GetWeakPtr(), - pictureBufferId))); + auto block = info->mGraphicBlock; + work->worklets.front()->output.buffers.emplace_back(C2Buffer::CreateGraphicBuffer( + block->share(C2Rect(mOutputFormat.mVisibleRect.width(), + mOutputFormat.mVisibleRect.height()), + C2Fence()))); // TODO: this does not work for timestamps as they can wrap around - int64_t currentTimestamp = base::checked_cast<int64_t>(work->input.ordinal.timestamp.peek()); + int64_t currentTimestamp = ::base::checked_cast<int64_t>(work->input.ordinal.timestamp.peek()); CHECK_GE(currentTimestamp, mLastOutputTimestamp); mLastOutputTimestamp = currentTimestamp; @@ -434,7 +448,7 @@ void C2VDAComponent::onDrain(uint32_t drainMode) { if (mComponentState == ComponentState::STARTED) { mVDAAdaptor->flush(); mComponentState = ComponentState::DRAINING; - mDrainWithEOS = drainMode == DRAIN_COMPONENT_WITH_EOS; + mPendingOutputEOS = drainMode == DRAIN_COMPONENT_WITH_EOS; } else { ALOGV("Neglect drain. Component in state: %d", mComponentState); } @@ -452,13 +466,15 @@ void C2VDAComponent::onDrainDone() { } else if (mComponentState == ComponentState::STOPPING) { // The client signals stop right before VDA notifies drain done. Let stop process goes. return; - } else { + } else if (mComponentState != ComponentState::FLUSHING) { + // It is reasonable to get onDrainDone in FLUSHING, which means flush is already signaled + // and component should still expect onFlushDone callback from VDA. ALOGE("Unexpected state while onDrainDone(). State=%d", mComponentState); reportError(C2_BAD_STATE); return; } - if (mDrainWithEOS) { + if (mPendingOutputEOS) { // Return EOS work. reportEOSWork(); } @@ -470,27 +486,28 @@ void C2VDAComponent::onDrainDone() { // Work dequeueing was stopped while component draining. Restart it. mTaskRunner->PostTask(FROM_HERE, - base::Bind(&C2VDAComponent::onDequeueWork, base::Unretained(this))); + ::base::Bind(&C2VDAComponent::onDequeueWork, ::base::Unretained(this))); } void C2VDAComponent::onFlush() { DCHECK(mTaskRunner->BelongsToCurrentThread()); ALOGV("onFlush"); - if (mComponentState == ComponentState::FLUSHING) { - return; // Ignore other flush request when component is flushing. + if (mComponentState == ComponentState::FLUSHING || + mComponentState == ComponentState::STOPPING) { + return; // Ignore other flush request when component is flushing or stopping. } - EXPECT_STATE_OR_RETURN_ON_ERROR(STARTED); + EXPECT_RUNNING_OR_RETURN_ON_ERROR(); mVDAAdaptor->reset(); - // Pop all works in mQueue and put into mPendingWorks. + // Pop all works in mQueue and put into mAbandonedWorks. while (!mQueue.empty()) { - mPendingWorks.emplace_back(std::move(mQueue.front().mWork)); + mAbandonedWorks.emplace_back(std::move(mQueue.front().mWork)); mQueue.pop(); } mComponentState = ComponentState::FLUSHING; } -void C2VDAComponent::onStop(base::WaitableEvent* done) { +void C2VDAComponent::onStop(::base::WaitableEvent* done) { DCHECK(mTaskRunner->BelongsToCurrentThread()); ALOGV("onStop"); EXPECT_RUNNING_OR_RETURN_ON_ERROR(); @@ -501,9 +518,9 @@ void C2VDAComponent::onStop(base::WaitableEvent* done) { mVDAAdaptor->reset(); } - // Pop all works in mQueue and put into mPendingWorks. + // Pop all works in mQueue and put into mAbandonedWorks. while (!mQueue.empty()) { - mPendingWorks.emplace_back(std::move(mQueue.front().mWork)); + mAbandonedWorks.emplace_back(std::move(mQueue.front().mWork)); mQueue.pop(); } @@ -531,15 +548,16 @@ void C2VDAComponent::onFlushDone() { // Reset the timestamp record. mLastOutputTimestamp = -1; mComponentState = ComponentState::STARTED; + + // Work dequeueing was stopped while component flushing. Restart it. + mTaskRunner->PostTask(FROM_HERE, + ::base::Bind(&C2VDAComponent::onDequeueWork, ::base::Unretained(this))); } void C2VDAComponent::onStopDone() { ALOGV("onStopDone"); CHECK(mStopDoneEvent); - // Release the graphic block allocator object. - mOutputBlockPool.reset(); - // TODO(johnylin): At this moment, there may be C2Buffer still owned by client, do we need to // do something for them? reportAbandonedWorks(); @@ -552,6 +570,9 @@ void C2VDAComponent::onStopDone() { mGraphicBlocks.clear(); + mMockBufferQueueInClient.clear(); // Hack(b/79239042) + stopDequeueThread(); + mStopDoneEvent->Signal(); mStopDoneEvent = nullptr; mComponentState = ComponentState::UNINITIALIZED; @@ -606,6 +627,19 @@ C2VDAComponent::GraphicBlockInfo* C2VDAComponent::getGraphicBlockById(int32_t bl return &mGraphicBlocks[blockId]; } +C2VDAComponent::GraphicBlockInfo* C2VDAComponent::getGraphicBlockBySlot(uint32_t slotId) { + auto blockIter = std::find_if(mGraphicBlocks.begin(), mGraphicBlocks.end(), + [slotId](const GraphicBlockInfo& gb) { + return gb.mSlotId == slotId; + }); + + if (blockIter == mGraphicBlocks.end()) { + ALOGE("getGraphicBlockBySlot failed: slot=%u", slotId); + return nullptr; + } + return &(*blockIter); +} + void C2VDAComponent::onOutputFormatChanged(std::unique_ptr<VideoFormat> format) { DCHECK(mTaskRunner->BelongsToCurrentThread()); ALOGV("onOutputFormatChanged"); @@ -666,30 +700,55 @@ c2_status_t C2VDAComponent::allocateBuffersFromBlockAllocator(const media::Size& uint32_t pixelFormat) { ALOGV("allocateBuffersFromBlockAllocator(%s, 0x%x)", size.ToString().c_str(), pixelFormat); + mMockBufferQueueInClient.clear(); // Hack(b/79239042) + stopDequeueThread(); + size_t bufferCount = mOutputFormat.mMinNumBuffers + kDpbOutputBufferExtraCount; // Allocate the output buffers. mVDAAdaptor->assignPictureBuffers(bufferCount); // Get block pool ID configured from the client. + std::shared_ptr<C2BlockPool> blockPool; auto poolId = mIntfImpl->getBlockPoolId(); ALOGI("Using C2BlockPool ID = %" PRIu64 " for allocating output buffers", poolId); - c2_status_t err; - if (!mOutputBlockPool || mOutputBlockPool->getLocalId() != poolId) { - err = GetCodec2BlockPool(poolId, shared_from_this(), &mOutputBlockPool); - if (err != C2_OK) { - ALOGE("Graphic block allocator is invalid"); - reportError(err); - return err; - } + auto err = GetCodec2BlockPool(poolId, shared_from_this(), &blockPool); + if (err != C2_OK) { + ALOGE("Graphic block allocator is invalid"); + reportError(err); + return err; } mGraphicBlocks.clear(); + + if (blockPool->getAllocatorId() == C2PlatformAllocatorStore::BUFFERQUEUE) { + // Set requested buffer count to C2VdaBqBlockPool. + std::shared_ptr<C2VdaBqBlockPool> bqPool = + std::static_pointer_cast<C2VdaBqBlockPool>(blockPool); + 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 { // CCodec falls back to use C2BasicGraphicBlockPool + ALOGD("CCodec falls back to use C2BasicGraphicBlockPool..."); + mSurfaceMode = false; + } + for (size_t i = 0; i < bufferCount; ++i) { std::shared_ptr<C2GraphicBlock> block; C2MemoryUsage usage = {C2MemoryUsage::CPU_READ, 0}; - err = mOutputBlockPool->fetchGraphicBlock(size.width(), size.height(), pixelFormat, usage, - &block); + err = blockPool->fetchGraphicBlock(size.width(), size.height(), pixelFormat, usage, &block); if (err != C2_OK) { mGraphicBlocks.clear(); ALOGE("failed to allocate buffer: %d", err); @@ -699,6 +758,11 @@ c2_status_t C2VDAComponent::allocateBuffersFromBlockAllocator(const media::Size& appendOutputBuffer(std::move(block)); } mOutputFormat.mMinNumBuffers = bufferCount; + + if (mSurfaceMode && !startDequeueThread(size, pixelFormat, std::move(blockPool))) { + reportError(C2_CORRUPTED); + return C2_CORRUPTED; + } return C2_OK; } @@ -752,7 +816,7 @@ void C2VDAComponent::appendOutputBuffer(std::shared_ptr<C2GraphicBlock> block) { #endif ALOGV("HAL pixel format: 0x%x", static_cast<uint32_t>(info.mPixelFormat)); - base::ScopedFD passedHandle(dup(info.mGraphicBlock->handle()->data[0])); + ::base::ScopedFD passedHandle(dup(info.mGraphicBlock->handle()->data[0])); if (!passedHandle.is_valid()) { ALOGE("Failed to dup(%d), errno=%d", info.mGraphicBlock->handle()->data[0], errno); reportError(C2_CORRUPTED); @@ -766,6 +830,12 @@ void C2VDAComponent::appendOutputBuffer(std::shared_ptr<C2GraphicBlock> block) { info.mHandle = std::move(passedHandle); info.mPlanes = std::move(passedPlanes); + 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)); } @@ -808,8 +878,8 @@ c2_status_t C2VDAComponent::queue_nb(std::list<std::unique_ptr<C2Work>>* const i } while (!items->empty()) { mTaskRunner->PostTask(FROM_HERE, - base::Bind(&C2VDAComponent::onQueueWork, base::Unretained(this), - base::Passed(&items->front()))); + ::base::Bind(&C2VDAComponent::onQueueWork, ::base::Unretained(this), + ::base::Passed(&items->front()))); items->pop_front(); } return C2_OK; @@ -828,7 +898,8 @@ c2_status_t C2VDAComponent::flush_sm(flush_mode_t mode, if (mState.load() != State::RUNNING) { return C2_BAD_STATE; } - mTaskRunner->PostTask(FROM_HERE, base::Bind(&C2VDAComponent::onFlush, base::Unretained(this))); + mTaskRunner->PostTask(FROM_HERE, ::base::Bind(&C2VDAComponent::onFlush, + ::base::Unretained(this))); // Instead of |flushedWork|, abandoned works will be returned via onWorkDone_nb() callback. return C2_OK; } @@ -840,8 +911,9 @@ c2_status_t C2VDAComponent::drain_nb(drain_mode_t mode) { if (mState.load() != State::RUNNING) { return C2_BAD_STATE; } - mTaskRunner->PostTask(FROM_HERE, base::Bind(&C2VDAComponent::onDrain, base::Unretained(this), - static_cast<uint32_t>(mode))); + mTaskRunner->PostTask(FROM_HERE, + ::base::Bind(&C2VDAComponent::onDrain, ::base::Unretained(this), + static_cast<uint32_t>(mode))); return C2_OK; } @@ -856,10 +928,11 @@ c2_status_t C2VDAComponent::start() { mCodecProfile = mIntfImpl->getCodecProfile(); ALOGI("get parameter: mCodecProfile = %d", static_cast<int>(mCodecProfile)); - base::WaitableEvent done(base::WaitableEvent::ResetPolicy::AUTOMATIC, - base::WaitableEvent::InitialState::NOT_SIGNALED); - mTaskRunner->PostTask(FROM_HERE, base::Bind(&C2VDAComponent::onStart, base::Unretained(this), - mCodecProfile, &done)); + ::base::WaitableEvent done(::base::WaitableEvent::ResetPolicy::AUTOMATIC, + ::base::WaitableEvent::InitialState::NOT_SIGNALED); + mTaskRunner->PostTask(FROM_HERE, + ::base::Bind(&C2VDAComponent::onStart, ::base::Unretained(this), + mCodecProfile, &done)); done.Wait(); if (mVDAInitResult != VideoDecodeAcceleratorAdaptor::Result::SUCCESS) { ALOGE("Failed to start component due to VDA error: %d", static_cast<int>(mVDAInitResult)); @@ -878,10 +951,10 @@ c2_status_t C2VDAComponent::stop() { return C2_OK; // Component is already in stopped state. } - base::WaitableEvent done(base::WaitableEvent::ResetPolicy::AUTOMATIC, - base::WaitableEvent::InitialState::NOT_SIGNALED); + ::base::WaitableEvent done(::base::WaitableEvent::ResetPolicy::AUTOMATIC, + ::base::WaitableEvent::InitialState::NOT_SIGNALED); mTaskRunner->PostTask(FROM_HERE, - base::Bind(&C2VDAComponent::onStop, base::Unretained(this), &done)); + ::base::Bind(&C2VDAComponent::onStop, ::base::Unretained(this), &done)); done.Wait(); mState.store(State::LOADED); return C2_OK; @@ -910,8 +983,9 @@ void C2VDAComponent::providePictureBuffers(uint32_t minNumBuffers, const media:: // Set mRequestedVisibleRect to default. mRequestedVisibleRect = media::Rect(); - mTaskRunner->PostTask(FROM_HERE, base::Bind(&C2VDAComponent::onOutputFormatChanged, - base::Unretained(this), base::Passed(&format))); + mTaskRunner->PostTask(FROM_HERE, ::base::Bind(&C2VDAComponent::onOutputFormatChanged, + ::base::Unretained(this), + ::base::Passed(&format))); } void C2VDAComponent::dismissPictureBuffer(int32_t pictureBufferId) { @@ -926,28 +1000,28 @@ void C2VDAComponent::pictureReady(int32_t pictureBufferId, int32_t bitstreamId, if (mRequestedVisibleRect != cropRect) { mRequestedVisibleRect = cropRect; - mTaskRunner->PostTask(FROM_HERE, base::Bind(&C2VDAComponent::onVisibleRectChanged, - base::Unretained(this), cropRect)); + mTaskRunner->PostTask(FROM_HERE, ::base::Bind(&C2VDAComponent::onVisibleRectChanged, + ::base::Unretained(this), cropRect)); } - mTaskRunner->PostTask(FROM_HERE, - base::Bind(&C2VDAComponent::onOutputBufferDone, base::Unretained(this), - pictureBufferId, bitstreamId)); + mTaskRunner->PostTask(FROM_HERE, ::base::Bind(&C2VDAComponent::onOutputBufferDone, + ::base::Unretained(this), + pictureBufferId, bitstreamId)); } void C2VDAComponent::notifyEndOfBitstreamBuffer(int32_t bitstreamId) { - mTaskRunner->PostTask(FROM_HERE, base::Bind(&C2VDAComponent::onInputBufferDone, - base::Unretained(this), bitstreamId)); + mTaskRunner->PostTask(FROM_HERE, ::base::Bind(&C2VDAComponent::onInputBufferDone, + ::base::Unretained(this), bitstreamId)); } void C2VDAComponent::notifyFlushDone() { mTaskRunner->PostTask(FROM_HERE, - base::Bind(&C2VDAComponent::onDrainDone, base::Unretained(this))); + ::base::Bind(&C2VDAComponent::onDrainDone, ::base::Unretained(this))); } void C2VDAComponent::notifyResetDone() { mTaskRunner->PostTask(FROM_HERE, - base::Bind(&C2VDAComponent::onResetDone, base::Unretained(this))); + ::base::Bind(&C2VDAComponent::onResetDone, ::base::Unretained(this))); } void C2VDAComponent::notifyError(VideoDecodeAcceleratorAdaptor::Result error) { @@ -1003,16 +1077,17 @@ void C2VDAComponent::reportFinishedWorkIfAny() { } 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 (mComponentState == ComponentState::DRAINING && mDrainWithEOS && - mPendingWorks.size() == 1u) { - // If component is in DRAINING state and mDrainWithEOS is true. The last returned work - // should be marked EOS flag and returned by reportEOSWork() instead. + if (mPendingOutputEOS && mPendingWorks.size() == 1u) { + // If mPendingOutputEOS is true, the last returned work should be marked EOS flag and + // returned by reportEOSWork() instead. return false; } if (mLastOutputTimestamp < 0) { @@ -1034,9 +1109,13 @@ void C2VDAComponent::reportEOSWork() { return; } + mPendingOutputEOS = false; + 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; @@ -1056,10 +1135,26 @@ void C2VDAComponent::reportAbandonedWorks() { // 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, 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(); + + // Pending EOS work will be abandoned here due to component flush if any. + mPendingOutputEOS = false; if (!abandonedWorks.empty()) { mListener->onWorkDone_nb(shared_from_this(), std::move(abandonedWorks)); @@ -1070,6 +1165,58 @@ void C2VDAComponent::reportError(c2_status_t error) { mListener->onError_nb(shared_from_this(), static_cast<uint32_t>(error)); } +bool C2VDAComponent::startDequeueThread(const media::Size& size, uint32_t pixelFormat, + std::shared_ptr<C2BlockPool> blockPool) { + CHECK(!mDequeueThread.IsRunning()); + if (!mDequeueThread.Start()) { + ALOGE("failed to start dequeue thread!!"); + return false; + } + mDequeueLoopStop.store(false); + mBuffersInClient.store(0u); + mDequeueThread.task_runner()->PostTask( + FROM_HERE, ::base::Bind(&C2VDAComponent::dequeueThreadLoop, ::base::Unretained(this), + size, pixelFormat, std::move(blockPool))); + return true; +} + +void C2VDAComponent::stopDequeueThread() { + if (mDequeueThread.IsRunning()) { + mDequeueLoopStop.store(true); + mDequeueThread.Stop(); + } +} + +void C2VDAComponent::dequeueThreadLoop(const media::Size& size, uint32_t pixelFormat, + std::shared_ptr<C2BlockPool> blockPool) { + ALOGV("dequeueThreadLoop starts"); + DCHECK(mDequeueThread.task_runner()->BelongsToCurrentThread()); + + while (!mDequeueLoopStop.load()) { + if (mBuffersInClient.load() == 0) { + ::usleep(kDequeueRetryDelayUs); // wait for retry + continue; + } + std::shared_ptr<C2GraphicBlock> block; + C2MemoryUsage usage = {C2MemoryUsage::CPU_READ, 0}; + auto err = blockPool->fetchGraphicBlock(size.width(), size.height(), pixelFormat, usage, + &block); + if (err == C2_TIMED_OUT) { + continue; // wait for retry + } + if (err == C2_OK) { + auto slot = getSlotFromGraphicBlockHandle(block->handle()); + mTaskRunner->PostTask(FROM_HERE, ::base::Bind(&C2VDAComponent::onOutputBufferReturned, + ::base::Unretained(this), slot)); + mBuffersInClient--; + } else { + ALOGE("dequeueThreadLoop got error: %d", err); + break; + } + } + ALOGV("dequeueThreadLoop terminates"); +} + class C2VDAComponentFactory : public C2ComponentFactory { public: C2VDAComponentFactory(C2String decoderName) |