| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //#define LOG_NDEBUG 0 |
| #define LOG_TAG "C2VdaBqBlockPool" |
| |
| #include <v4l2_codec2/plugin_store/C2VdaBqBlockPool.h> |
| |
| #include <errno.h> |
| |
| #include <chrono> |
| #include <mutex> |
| |
| #include <C2AllocatorGralloc.h> |
| #include <C2BlockInternal.h> |
| #include <log/log.h> |
| #include <system/window.h> |
| #include <types.h> |
| #include <ui/BufferQueueDefs.h> |
| |
| #include <v4l2_codec2/plugin_store/V4L2AllocatorId.h> |
| |
| namespace android { |
| namespace { |
| |
| // The wait time for acquire fence in milliseconds. |
| constexpr int kFenceWaitTimeMs = 10; |
| // The timeout delay range for dequeuing spare buffer delay time in microseconds. |
| constexpr int kDequeueSpareMinDelayUs = 500; |
| constexpr int kDequeueSpareMaxDelayUs = 16 * 1000; |
| // The timeout limit of acquiring lock of timed_mutex in milliseconds. |
| constexpr std::chrono::milliseconds kTimedMutexTimeoutMs = std::chrono::milliseconds(500); |
| // The max retry times for fetchSpareBufferSlot timeout. |
| constexpr int32_t kFetchSpareBufferMaxRetries = 10; |
| |
| } // namespace |
| |
| using ::android::C2AndroidMemoryUsage; |
| using ::android::Fence; |
| using ::android::GraphicBuffer; |
| using ::android::sp; |
| using ::android::status_t; |
| using ::android::BufferQueueDefs::BUFFER_NEEDS_REALLOCATION; |
| using ::android::BufferQueueDefs::NUM_BUFFER_SLOTS; |
| using ::android::hardware::hidl_handle; |
| using ::android::hardware::Return; |
| |
| using HBuffer = ::android::hardware::graphics::common::V1_2::HardwareBuffer; |
| using HStatus = ::android::hardware::graphics::bufferqueue::V2_0::Status; |
| using ::android::hardware::graphics::bufferqueue::V2_0::utils::b2h; |
| using ::android::hardware::graphics::bufferqueue::V2_0::utils::h2b; |
| using ::android::hardware::graphics::bufferqueue::V2_0::utils::HFenceWrapper; |
| |
| static c2_status_t asC2Error(int32_t err) { |
| switch (err) { |
| case android::NO_ERROR: |
| return C2_OK; |
| case android::NO_INIT: |
| return C2_NO_INIT; |
| case android::BAD_VALUE: |
| return C2_BAD_VALUE; |
| case android::TIMED_OUT: |
| return C2_TIMED_OUT; |
| case android::WOULD_BLOCK: |
| return C2_BLOCKING; |
| case android::NO_MEMORY: |
| return C2_NO_MEMORY; |
| } |
| return C2_CORRUPTED; |
| } |
| |
| /** |
| * BlockPoolData implementation for C2VdaBqBlockPool. The life cycle of this object should be as |
| * long as its accompanied C2GraphicBlock. |
| * |
| * When C2VdaBqBlockPoolData is created, |mShared| is false, and the owner of the accompanied |
| * C2GraphicBlock is the component that called fetchGraphicBlock(). If this is released before |
| * sharing, the destructor will call detachBuffer() to BufferQueue to free the slot. |
| * |
| * When the accompanied C2GraphicBlock is going to share to client from component, component should |
| * call MarkBlockPoolDataAsShared() to set |mShared| to true, and then this will be released after |
| * the transition of C2GraphicBlock across HIDL interface. At this time, the destructor will not |
| * call detachBuffer(). |
| */ |
| struct C2VdaBqBlockPoolData : public _C2BlockPoolData { |
| // This type should be a different value than what _C2BlockPoolData::type_t has defined. |
| static constexpr int kTypeVdaBufferQueue = TYPE_BUFFERQUEUE + 256; |
| |
| C2VdaBqBlockPoolData(uint64_t producerId, int32_t slotId, |
| const std::shared_ptr<C2VdaBqBlockPool::Impl>& pool); |
| C2VdaBqBlockPoolData() = delete; |
| |
| // If |mShared| is false, call detach buffer to BufferQueue via |mPool| |
| virtual ~C2VdaBqBlockPoolData() override; |
| |
| type_t getType() const override { return static_cast<type_t>(kTypeVdaBufferQueue); } |
| |
| bool mShared = false; // whether is shared from component to client. |
| const uint64_t mProducerId; |
| const int32_t mSlotId; |
| const std::shared_ptr<C2VdaBqBlockPool::Impl> mPool; |
| }; |
| |
| c2_status_t MarkBlockPoolDataAsShared(const C2ConstGraphicBlock& sharedBlock) { |
| std::shared_ptr<_C2BlockPoolData> data = _C2BlockFactory::GetGraphicBlockPoolData(sharedBlock); |
| if (!data || data->getType() != C2VdaBqBlockPoolData::kTypeVdaBufferQueue) { |
| // Skip this functtion if |sharedBlock| is not fetched from C2VdaBqBlockPool. |
| return C2_OMITTED; |
| } |
| const std::shared_ptr<C2VdaBqBlockPoolData> poolData = |
| std::static_pointer_cast<C2VdaBqBlockPoolData>(data); |
| if (poolData->mShared) { |
| ALOGE("C2VdaBqBlockPoolData(id=%" PRIu64 ", slot=%d) is already marked as shared...", |
| poolData->mProducerId, poolData->mSlotId); |
| return C2_BAD_STATE; |
| } |
| poolData->mShared = true; |
| return C2_OK; |
| } |
| |
| // static |
| std::optional<uint32_t> C2VdaBqBlockPool::getBufferIdFromGraphicBlock(const C2Block2D& block) { |
| uint32_t width, height, format, stride, igbp_slot, generation; |
| uint64_t usage, igbp_id; |
| android::_UnwrapNativeCodec2GrallocMetadata(block.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; |
| } |
| |
| class C2VdaBqBlockPool::Impl : public std::enable_shared_from_this<C2VdaBqBlockPool::Impl> { |
| public: |
| using HGraphicBufferProducer = C2VdaBqBlockPool::HGraphicBufferProducer; |
| |
| explicit Impl(const std::shared_ptr<C2Allocator>& allocator); |
| // TODO: should we detach buffers on producer if any on destructor? |
| ~Impl() = default; |
| |
| c2_status_t fetchGraphicBlock(uint32_t width, uint32_t height, uint32_t format, |
| C2MemoryUsage usage, |
| std::shared_ptr<C2GraphicBlock>* block /* nonnull */); |
| void setRenderCallback(const C2BufferQueueBlockPool::OnRenderCallback& renderCallback); |
| void configureProducer(const sp<HGraphicBufferProducer>& producer); |
| c2_status_t requestNewBufferSet(int32_t bufferCount); |
| c2_status_t updateGraphicBlock(bool willCancel, uint32_t oldSlot, uint32_t* newSlot, |
| std::shared_ptr<C2GraphicBlock>* block /* nonnull */); |
| c2_status_t getMinBuffersForDisplay(size_t* bufferCount); |
| |
| private: |
| friend struct C2VdaBqBlockPoolData; |
| |
| // The exponential rate control calculator with factor of 2. Per increase() call will double the |
| // value until it reaches maximum. reset() will set value to the minimum. |
| class ExpRateControlCalculator { |
| public: |
| ExpRateControlCalculator(int min, int max) : kMinValue(min), kMaxValue(max), mValue(min) {} |
| ExpRateControlCalculator() = delete; |
| |
| void reset() { mValue = kMinValue; } |
| void increase() { mValue = std::min(kMaxValue, mValue << 1); } |
| int value() const { return mValue; } |
| |
| private: |
| const int kMinValue; |
| const int kMaxValue; |
| int mValue; |
| }; |
| |
| // Requested buffer formats. |
| struct BufferFormat { |
| BufferFormat(uint32_t width, uint32_t height, uint32_t pixelFormat, |
| C2AndroidMemoryUsage androidUsage) |
| : mWidth(width), mHeight(height), mPixelFormat(pixelFormat), mUsage(androidUsage) {} |
| BufferFormat() = default; |
| |
| uint32_t mWidth = 0; |
| uint32_t mHeight = 0; |
| uint32_t mPixelFormat = 0; |
| C2AndroidMemoryUsage mUsage = C2MemoryUsage(0); |
| }; |
| |
| // For C2VdaBqBlockPoolData to detach corresponding slot buffer from BufferQueue. |
| void detachBuffer(uint64_t producerId, int32_t slotId); |
| |
| // Fetches a spare slot index by dequeueing and requesting one extra buffer from producer. The |
| // spare buffer slot guarantees at least one buffer to be dequeued in producer, so as to prevent |
| // the invalid operation for producer of the attempt to dequeue buffers exceeded the maximal |
| // dequeued buffer count. |
| // This function should be called after the last requested buffer is fetched in |
| // fetchGraphicBlock(), or in the beginning of switchProducer(). Block pool should store the |
| // slot index into |mSpareSlot| and cancel the buffer immediately. |
| // The generation number and usage of the spare buffer will be recorded in |generation| and |
| // |usage|, which will be useful later in switchProducer(). |
| c2_status_t fetchSpareBufferSlot(HGraphicBufferProducer* const producer, uint32_t width, |
| uint32_t height, uint32_t pixelFormat, |
| C2AndroidMemoryUsage androidUsage, uint32_t* generation, |
| uint64_t* usage); |
| |
| // Helper function to call dequeue buffer to producer. |
| c2_status_t dequeueBuffer(HGraphicBufferProducer* const producer, uint32_t width, |
| uint32_t height, uint32_t pixelFormat, |
| C2AndroidMemoryUsage androidUsage, int32_t& status, int32_t& slot, |
| sp<Fence>& fence); |
| |
| // Switches producer and transfers allocated buffers from old producer to the new one. |
| bool switchProducer(HGraphicBufferProducer* const newProducer, uint64_t newProducerId); |
| |
| const std::shared_ptr<C2Allocator> mAllocator; |
| |
| sp<HGraphicBufferProducer> mProducer; |
| uint64_t mProducerId; |
| C2BufferQueueBlockPool::OnRenderCallback mRenderCallback; |
| |
| // Function mutex to lock at the start of each API function call for protecting the |
| // synchronization of all member variables. |
| std::mutex mMutex; |
| // The mutex of excluding the procedures of configuring producer and allocating buffers. They |
| // should be blocked mutually. Set the timeout for acquiring lock in case of any deadlock. |
| // Configuring producer: configureProducer() called by CCodec. |
| // Allocating buffers: requestNewBufferSet(), then a loop of fetchGraphicBlock() called by |
| // compoenent until |mSlotAllocations|.size() equals |mBuffersRequested|. |
| std::timed_mutex mConfigureProducerAndAllocateBuffersMutex; |
| // The unique lock of the procedure of allocating buffers. It should be locked in the beginning |
| // of requestNewBufferSet() and unlock in the end of the loop of fetchGraphicBlock(). Note that |
| // all calls should be in the same thread. |
| std::unique_lock<std::timed_mutex> mAllocateBuffersLock; |
| |
| // The map restored C2GraphicAllocation from corresponding slot index. |
| std::map<int32_t, std::shared_ptr<C2GraphicAllocation>> mSlotAllocations; |
| // Number of buffers requested on requestNewBufferSet() call. |
| size_t mBuffersRequested; |
| // The slot index of spare buffer. |
| int32_t mSpareSlot; |
| // Currently requested buffer formats. |
| BufferFormat mBufferFormat; |
| // The map recorded the slot indices from old producer to new producer. |
| std::map<int32_t, int32_t> mProducerChangeSlotMap; |
| // The rate control calculator for the delay of dequeueing spare buffer. |
| ExpRateControlCalculator mSpareDequeueDelayUs; |
| // The counter for representing the buffer count in client. Only used in producer switching |
| // case. It will be reset in switchProducer(), and accumulated in updateGraphicBlock() routine. |
| uint32_t mBuffersInClient = 0u; |
| // The indicator to record if producer has been switched. Set to true when producer is switched. |
| // Toggle off when requestNewBufferSet() is called. We forcedly detach all slots to make sure |
| // all slots are available, except the ones owned by client. |
| bool mProducerSwitched = false; |
| }; |
| |
| C2VdaBqBlockPool::Impl::Impl(const std::shared_ptr<C2Allocator>& allocator) |
| : mAllocator(allocator), |
| mAllocateBuffersLock(mConfigureProducerAndAllocateBuffersMutex, std::defer_lock), |
| mBuffersRequested(0u), |
| mSpareSlot(-1), |
| mSpareDequeueDelayUs(kDequeueSpareMinDelayUs, kDequeueSpareMaxDelayUs) {} |
| |
| c2_status_t C2VdaBqBlockPool::Impl::fetchGraphicBlock( |
| uint32_t width, uint32_t height, uint32_t format, C2MemoryUsage usage, |
| std::shared_ptr<C2GraphicBlock>* block /* nonnull */) { |
| std::lock_guard<std::mutex> lock(mMutex); |
| |
| if (!mProducer) { |
| // Producer will not be configured in byte-buffer mode. Allocate buffers from allocator |
| // directly as a basic graphic block pool. |
| std::shared_ptr<C2GraphicAllocation> alloc; |
| c2_status_t err = mAllocator->newGraphicAllocation(width, height, format, usage, &alloc); |
| if (err != C2_OK) { |
| return err; |
| } |
| *block = _C2BlockFactory::CreateGraphicBlock(alloc); |
| return C2_OK; |
| } |
| |
| // The existence of |mProducerChangeSlotMap| indicates producer is just switched. Use return |
| // code C2_BAD_STATE to inform the component to handle the procedure of producer change. |
| // TODO(johnylin): consider to inform producer change to component in an active way. |
| if (!mProducerChangeSlotMap.empty()) { |
| return C2_BAD_STATE; |
| } |
| |
| sp<Fence> fence = new Fence(); |
| C2AndroidMemoryUsage androidUsage = usage; |
| int32_t status; |
| uint32_t pixelFormat = format; |
| int32_t slot; |
| |
| c2_status_t err = dequeueBuffer(mProducer.get(), width, height, pixelFormat, androidUsage, |
| status, slot, fence); |
| if (err != C2_OK) { |
| return err; |
| } |
| |
| // Wait for acquire fence if we get one. |
| HFenceWrapper hFenceWrapper{}; |
| if (!b2h(fence, &hFenceWrapper)) { |
| ALOGE("Invalid fence received from dequeueBuffer."); |
| return C2_BAD_VALUE; |
| } |
| if (fence) { |
| status_t fenceStatus = fence->wait(kFenceWaitTimeMs); |
| if (fenceStatus != android::NO_ERROR) { |
| Return<HStatus> cancelTransStatus = |
| mProducer->cancelBuffer(slot, hFenceWrapper.getHandle()); |
| if (!cancelTransStatus.isOk()) { |
| ALOGE("cancelBuffer transaction error: %s", |
| cancelTransStatus.description().c_str()); |
| return C2_CORRUPTED; |
| } |
| if (fenceStatus == -ETIME) { // fence wait timed out |
| ALOGV("buffer fence wait timed out, wait for retry..."); |
| return C2_TIMED_OUT; |
| } |
| ALOGE("buffer fence wait error: %d", fenceStatus); |
| return asC2Error(fenceStatus); |
| } |
| if (mRenderCallback) { |
| nsecs_t signalTime = fence->getSignalTime(); |
| if (signalTime >= 0 && signalTime < INT64_MAX) { |
| mRenderCallback(mProducerId, slot, signalTime); |
| } else { |
| ALOGV("got fence signal time of %" PRId64 " nsec", signalTime); |
| } |
| } |
| } |
| |
| auto iter = mSlotAllocations.find(slot); |
| if (iter == mSlotAllocations.end()) { |
| if (slot == mSpareSlot) { |
| // The dequeued slot is the spare buffer, we don't use this buffer for decoding and must |
| // cancel it after the delay time. Other working buffers may be available and pushed to |
| // free buffer queue in producer during the delay. |
| ALOGV("dequeued spare slot, cancel it after a wait time delay (%d)...", |
| mSpareDequeueDelayUs.value()); |
| ::usleep(mSpareDequeueDelayUs.value()); // wait for retry |
| // Double the delay time if spare buffer still be dequeued the next time. This could |
| // prevent block pool keeps aggressively dequeueing spare buffer while other buffers are |
| // not available yet. |
| mSpareDequeueDelayUs.increase(); |
| Return<HStatus> cancelTransStatus = |
| mProducer->cancelBuffer(slot, hFenceWrapper.getHandle()); |
| if (!cancelTransStatus.isOk()) { |
| ALOGE("cancelBuffer transaction error: %s", |
| cancelTransStatus.description().c_str()); |
| return C2_CORRUPTED; |
| } |
| return C2_TIMED_OUT; |
| } |
| if (mSlotAllocations.size() >= mBuffersRequested) { |
| // The dequeued slot has a pre-allocated buffer whose size and format is as same as |
| // currently requested (but was not dequeued during allocation cycle). Just detach it to |
| // free this slot. And try dequeueBuffer again. |
| ALOGD("dequeued a new slot index but already allocated enough buffers. Detach it."); |
| Return<HStatus> detachTransStatus = mProducer->detachBuffer(slot); |
| if (!detachTransStatus.isOk()) { |
| ALOGE("detachBuffer transaction error: %s", |
| detachTransStatus.description().c_str()); |
| return C2_CORRUPTED; |
| } |
| return C2_TIMED_OUT; |
| } |
| if (status != BUFFER_NEEDS_REALLOCATION) { |
| // The dequeued slot has a pre-allocated buffer whose size and format is as same as |
| // currently requested, so there is no BUFFER_NEEDS_REALLOCATION flag. However since the |
| // buffer reference is already dropped, still call requestBuffer to re-allocate then. |
| // Add a debug note here for tracking. |
| ALOGD("dequeued a new slot index without BUFFER_NEEDS_REALLOCATION flag."); |
| } |
| |
| // Call requestBuffer to allocate buffer for the slot and obtain the reference. |
| sp<GraphicBuffer> slotBuffer = new GraphicBuffer(); |
| uint32_t generation; |
| Return<void> transStatus = mProducer->requestBuffer( |
| slot, [&status, &slotBuffer, &generation](HStatus hStatus, HBuffer const& hBuffer, |
| uint32_t generationNumber) { |
| if (h2b(hStatus, &status) && h2b(hBuffer, &slotBuffer) && slotBuffer) { |
| generation = generationNumber; |
| slotBuffer->setGenerationNumber(generationNumber); |
| } else { |
| status = android::BAD_VALUE; |
| } |
| }); |
| |
| // Check requestBuffer transaction status |
| if (!transStatus.isOk()) { |
| ALOGE("requestBuffer transaction error: %s", transStatus.description().c_str()); |
| return C2_CORRUPTED; |
| } |
| // Check requestBuffer return flag |
| if (status != android::NO_ERROR) { |
| ALOGE("requestBuffer failed: %d", status); |
| Return<HStatus> cancelTransStatus = |
| mProducer->cancelBuffer(slot, hFenceWrapper.getHandle()); |
| if (!cancelTransStatus.isOk()) { |
| ALOGE("cancelBuffer transaction error: %s", |
| cancelTransStatus.description().c_str()); |
| return C2_CORRUPTED; |
| } |
| return asC2Error(status); |
| } |
| |
| // Convert GraphicBuffer to C2GraphicAllocation and wrap producer id and slot index |
| ALOGV("buffer wraps { producer id: %" PRIu64 ", slot: %d }", mProducerId, slot); |
| C2Handle* c2Handle = android::WrapNativeCodec2GrallocHandle( |
| slotBuffer->handle, slotBuffer->width, slotBuffer->height, slotBuffer->format, |
| slotBuffer->usage, slotBuffer->stride, slotBuffer->getGenerationNumber(), |
| mProducerId, slot); |
| if (!c2Handle) { |
| ALOGE("WrapNativeCodec2GrallocHandle failed"); |
| return C2_NO_MEMORY; |
| } |
| |
| std::shared_ptr<C2GraphicAllocation> alloc; |
| c2_status_t err = mAllocator->priorGraphicAllocation(c2Handle, &alloc); |
| if (err != C2_OK) { |
| ALOGE("priorGraphicAllocation failed: %d", err); |
| return err; |
| } |
| |
| mSlotAllocations[slot] = std::move(alloc); |
| if (mSlotAllocations.size() == mBuffersRequested) { |
| // Allocate one spare buffer after allocating enough buffers requested by client. |
| uint32_t generation; |
| uint64_t usage; |
| |
| err = C2_TIMED_OUT; |
| for (int32_t retriesLeft = kFetchSpareBufferMaxRetries; |
| err == C2_TIMED_OUT && retriesLeft >= 0; retriesLeft--) { |
| err = fetchSpareBufferSlot(mProducer.get(), width, height, pixelFormat, |
| androidUsage, &generation, &usage); |
| } |
| if (err != C2_OK) { |
| ALOGE("fetchSpareBufferSlot failed after %d retries: %d", |
| kFetchSpareBufferMaxRetries, err); |
| return err; |
| } |
| |
| // Already allocated enough buffers, set allowAllocation to false to restrict the |
| // eligible slots to allocated ones for future dequeue. |
| Return<HStatus> transStatus = mProducer->allowAllocation(false); |
| if (!transStatus.isOk()) { |
| ALOGE("allowAllocation(false) transaction error: %s", |
| transStatus.description().c_str()); |
| return C2_CORRUPTED; |
| } |
| if (!h2b(static_cast<HStatus>(transStatus), &status)) { |
| status = android::BAD_VALUE; |
| } |
| if (status != android::NO_ERROR) { |
| ALOGE("allowAllocation(false) failed"); |
| return asC2Error(status); |
| } |
| // Store buffer formats for future usage. |
| mBufferFormat = BufferFormat(width, height, pixelFormat, androidUsage); |
| ALOG_ASSERT(mAllocateBuffersLock.owns_lock()); |
| mAllocateBuffersLock.unlock(); |
| } |
| } |
| |
| // Reset spare dequeue delay time once we have dequeued a working buffer. |
| mSpareDequeueDelayUs.reset(); |
| |
| auto poolData = std::make_shared<C2VdaBqBlockPoolData>(mProducerId, slot, shared_from_this()); |
| *block = _C2BlockFactory::CreateGraphicBlock(mSlotAllocations[slot], std::move(poolData)); |
| return C2_OK; |
| } |
| |
| c2_status_t C2VdaBqBlockPool::Impl::fetchSpareBufferSlot(HGraphicBufferProducer* const producer, |
| uint32_t width, uint32_t height, |
| uint32_t pixelFormat, |
| C2AndroidMemoryUsage androidUsage, |
| uint32_t* generation, uint64_t* usage) { |
| ALOGV("fetchSpareBufferSlot"); |
| sp<Fence> fence = new Fence(); |
| int32_t status; |
| int32_t slot; |
| |
| c2_status_t err = |
| dequeueBuffer(producer, width, height, pixelFormat, androidUsage, status, slot, fence); |
| if (err != C2_OK) { |
| return err; |
| } |
| |
| // Wait for acquire fence if we get one. |
| HFenceWrapper hFenceWrapper{}; |
| if (!b2h(fence, &hFenceWrapper)) { |
| ALOGE("Invalid fence received from dequeueBuffer."); |
| return C2_BAD_VALUE; |
| } |
| if (fence) { |
| status_t fenceStatus = fence->wait(kFenceWaitTimeMs); |
| if (fenceStatus != android::NO_ERROR) { |
| Return<HStatus> cancelTransStatus = |
| producer->cancelBuffer(slot, hFenceWrapper.getHandle()); |
| if (!cancelTransStatus.isOk()) { |
| ALOGE("cancelBuffer transaction error: %s", |
| cancelTransStatus.description().c_str()); |
| return C2_CORRUPTED; |
| } |
| if (fenceStatus == -ETIME) { // fence wait timed out |
| ALOGV("buffer fence wait timed out, wait for retry..."); |
| return C2_TIMED_OUT; |
| } |
| ALOGE("buffer fence wait error: %d", fenceStatus); |
| return asC2Error(fenceStatus); |
| } |
| } |
| |
| if (status != BUFFER_NEEDS_REALLOCATION) { |
| ALOGD("dequeued a new slot index without BUFFER_NEEDS_REALLOCATION flag."); |
| } |
| |
| // Call requestBuffer to allocate buffer for the slot and obtain the reference. |
| // Get generation number here. |
| sp<GraphicBuffer> slotBuffer = new GraphicBuffer(); |
| Return<void> transStatus = producer->requestBuffer( |
| slot, [&status, &slotBuffer, &generation](HStatus hStatus, HBuffer const& hBuffer, |
| uint32_t generationNumber) { |
| if (h2b(hStatus, &status) && h2b(hBuffer, &slotBuffer) && slotBuffer) { |
| *generation = generationNumber; |
| slotBuffer->setGenerationNumber(generationNumber); |
| } else { |
| status = android::BAD_VALUE; |
| } |
| }); |
| |
| // Check requestBuffer transaction status. |
| if (!transStatus.isOk()) { |
| ALOGE("requestBuffer transaction error: %s", transStatus.description().c_str()); |
| return C2_CORRUPTED; |
| } |
| |
| // Get generation number and usage from the slot buffer. |
| *usage = slotBuffer->getUsage(); |
| ALOGV("Obtained from spare buffer: generation = %u, usage = %" PRIu64 "", *generation, *usage); |
| |
| // Cancel this buffer anyway. |
| Return<HStatus> cancelTransStatus = producer->cancelBuffer(slot, hFenceWrapper.getHandle()); |
| if (!cancelTransStatus.isOk()) { |
| ALOGE("cancelBuffer transaction error: %s", cancelTransStatus.description().c_str()); |
| return C2_CORRUPTED; |
| } |
| |
| // Check requestBuffer return flag. |
| if (status != android::NO_ERROR) { |
| ALOGE("requestBuffer failed: %d", status); |
| return asC2Error(status); |
| } |
| |
| mSpareSlot = slot; |
| mSpareDequeueDelayUs.reset(); |
| ALOGV("Spare slot index = %d", mSpareSlot); |
| return C2_OK; |
| } |
| |
| c2_status_t C2VdaBqBlockPool::Impl::dequeueBuffer(HGraphicBufferProducer* const producer, |
| uint32_t width, uint32_t height, |
| uint32_t pixelFormat, |
| C2AndroidMemoryUsage androidUsage, |
| int32_t& status, int32_t& slot, |
| sp<Fence>& fence) { |
| using Input = HGraphicBufferProducer::DequeueBufferInput; |
| using Output = HGraphicBufferProducer::DequeueBufferOutput; |
| bool needRealloc = false; |
| Return<void> transStatus = producer->dequeueBuffer( |
| Input{width, height, pixelFormat, androidUsage.asGrallocUsage()}, |
| [&status, &slot, &needRealloc, &fence](HStatus hStatus, int32_t hSlot, |
| Output const& hOutput) { |
| slot = hSlot; |
| if (!h2b(hStatus, &status) || !h2b(hOutput.fence, &fence)) { |
| status = android::BAD_VALUE; |
| } else { |
| needRealloc = hOutput.bufferNeedsReallocation; |
| if (needRealloc) { |
| status = BUFFER_NEEDS_REALLOCATION; |
| } |
| } |
| }); |
| |
| // Check dequeueBuffer transaction status |
| if (!transStatus.isOk()) { |
| ALOGE("dequeueBuffer transaction error: %s", transStatus.description().c_str()); |
| return C2_CORRUPTED; |
| } |
| // Check dequeueBuffer return flag |
| if (status != android::NO_ERROR && status != BUFFER_NEEDS_REALLOCATION) { |
| ALOGE("dequeueBuffer failed: %d", status); |
| return asC2Error(status); |
| } |
| return C2_OK; |
| } |
| |
| void C2VdaBqBlockPool::Impl::setRenderCallback( |
| const C2BufferQueueBlockPool::OnRenderCallback& renderCallback) { |
| ALOGV("setRenderCallback"); |
| std::lock_guard<std::mutex> lock(mMutex); |
| mRenderCallback = renderCallback; |
| } |
| |
| c2_status_t C2VdaBqBlockPool::Impl::requestNewBufferSet(int32_t bufferCount) { |
| if (bufferCount <= 0) { |
| ALOGE("Invalid requested buffer count = %d", bufferCount); |
| return C2_BAD_VALUE; |
| } |
| |
| if (!mAllocateBuffersLock.try_lock_for(kTimedMutexTimeoutMs)) { |
| ALOGE("Cannot acquire allocate buffers / configure producer lock over %" PRId64 " ms...", |
| static_cast<int64_t>(kTimedMutexTimeoutMs.count())); |
| return C2_BLOCKING; |
| } |
| |
| std::lock_guard<std::mutex> lock(mMutex); |
| if (!mProducer) { |
| ALOGD("No HGraphicBufferProducer is configured..."); |
| return C2_NO_INIT; |
| } |
| |
| if (mProducerSwitched) { |
| // Some slots can be occupied by buffers transferred from the old producer. They will not |
| // used in the current producer. Free the slots of the buffers here. But we cannot find a |
| // slot is associated with the staled buffer. We free all slots whose associated buffers |
| // are not owned by client. |
| ALOGI("requestNewBufferSet: detachBuffer all slots forcedly"); |
| for (int32_t slot = 0; slot < static_cast<int32_t>(NUM_BUFFER_SLOTS); ++slot) { |
| if (mSlotAllocations.find(slot) != mSlotAllocations.end()) { |
| // Skip detaching the buffer which is owned by client now. |
| continue; |
| } |
| Return<HStatus> transStatus = mProducer->detachBuffer(slot); |
| if (!transStatus.isOk()) { |
| ALOGE("detachBuffer trans error: %s", transStatus.description().c_str()); |
| return C2_CORRUPTED; |
| } |
| int32_t status; |
| if (!h2b(static_cast<HStatus>(transStatus), &status)) { |
| status = android::BAD_VALUE; |
| } |
| if (status == android::NO_INIT) { |
| // No more active buffer slot. Break the loop now. |
| break; |
| } |
| } |
| mProducerSwitched = false; |
| } |
| |
| ALOGV("Requested new buffer count: %d, still dequeued buffer count: %zu", bufferCount, |
| mSlotAllocations.size()); |
| |
| // The remained slot indices in |mSlotAllocations| now are still dequeued (un-available). |
| // maxDequeuedBufferCount should be set to "new requested buffer count" + "still dequeued buffer |
| // count" to make sure it has enough available slots to request buffer from. |
| // Moreover, one extra buffer count is added for fetching spare buffer slot index. |
| Return<HStatus> transStatus = |
| mProducer->setMaxDequeuedBufferCount(bufferCount + mSlotAllocations.size() + 1); |
| if (!transStatus.isOk()) { |
| ALOGE("setMaxDequeuedBufferCount trans error: %s", transStatus.description().c_str()); |
| return C2_CORRUPTED; |
| } |
| int32_t status; |
| if (!h2b(static_cast<HStatus>(transStatus), &status)) { |
| status = android::BAD_VALUE; |
| } |
| if (status != android::NO_ERROR) { |
| ALOGE("setMaxDequeuedBufferCount failed"); |
| return asC2Error(status); |
| } |
| |
| // Release all remained slot buffer references here. CCodec should either cancel or queue its |
| // owned buffers from this set before the next resolution change. |
| mSlotAllocations.clear(); |
| mProducerChangeSlotMap.clear(); |
| mBuffersRequested = static_cast<size_t>(bufferCount); |
| mSpareSlot = -1; |
| |
| Return<HStatus> transStatus2 = mProducer->allowAllocation(true); |
| if (!transStatus2.isOk()) { |
| ALOGE("allowAllocation(true) transaction error: %s", transStatus2.description().c_str()); |
| return C2_CORRUPTED; |
| } |
| if (!h2b(static_cast<HStatus>(transStatus2), &status)) { |
| status = android::BAD_VALUE; |
| } |
| if (status != android::NO_ERROR) { |
| ALOGE("allowAllocation(true) failed"); |
| return asC2Error(status); |
| } |
| return C2_OK; |
| } |
| |
| void C2VdaBqBlockPool::Impl::configureProducer(const sp<HGraphicBufferProducer>& producer) { |
| ALOGV("configureProducer"); |
| if (producer == nullptr) { |
| ALOGE("input producer is nullptr..."); |
| return; |
| } |
| |
| std::unique_lock<std::timed_mutex> configureProducerLock( |
| mConfigureProducerAndAllocateBuffersMutex, std::defer_lock); |
| if (!configureProducerLock.try_lock_for(kTimedMutexTimeoutMs)) { |
| ALOGE("Cannot acquire configure producer / allocate buffers lock over %" PRId64 " ms...", |
| static_cast<int64_t>(kTimedMutexTimeoutMs.count())); |
| return; |
| } |
| |
| std::lock_guard<std::mutex> lock(mMutex); |
| uint64_t producerId; |
| Return<uint64_t> transStatus = producer->getUniqueId(); |
| if (!transStatus.isOk()) { |
| ALOGE("getUniqueId transaction error: %s", transStatus.description().c_str()); |
| return; |
| } |
| producerId = static_cast<uint64_t>(transStatus); |
| |
| if (mProducer && mProducerId != producerId) { |
| ALOGI("Producer (Surface) is going to switch... ( %" PRIu64 " -> %" PRIu64 " )", |
| mProducerId, producerId); |
| if (!switchProducer(producer.get(), producerId)) { |
| mProducerChangeSlotMap.clear(); |
| return; |
| } |
| } else { |
| mSlotAllocations.clear(); |
| } |
| |
| // HGraphicBufferProducer could (and should) be replaced if the client has set a new generation |
| // number to producer. The old HGraphicBufferProducer will be disconnected and deprecated then. |
| mProducer = producer; |
| mProducerId = producerId; |
| } |
| |
| bool C2VdaBqBlockPool::Impl::switchProducer(HGraphicBufferProducer* const newProducer, |
| uint64_t newProducerId) { |
| if (mAllocator->getId() == android::V4L2AllocatorId::SECURE_GRAPHIC) { |
| // TODO(johnylin): support this when we meet the use case in the future. |
| ALOGE("Switch producer for secure buffer is not supported..."); |
| return false; |
| } |
| |
| // Set maxDequeuedBufferCount to new producer. |
| // Just like requestNewBufferSet(), maxDequeuedBufferCount should be set to "requested buffer |
| // count" + "buffer count in client" + 1 (spare buffer) to make sure it has enough available |
| // slots to request buffer from. |
| // "Requested buffer count" could be obtained by the size of |mSlotAllocations|. However, it is |
| // not able to know "buffer count in client" in blockpool's aspect. The alternative solution is |
| // to set the worse case first, which is equal to the size of |mSlotAllocations|. And in the end |
| // of updateGraphicBlock() routine, we could get the arbitrary "buffer count in client" by |
| // counting the calls of updateGraphicBlock(willCancel=true). Then we set maxDequeuedBufferCount |
| // again to the correct value. |
| Return<HStatus> transStatus = |
| newProducer->setMaxDequeuedBufferCount(mSlotAllocations.size() * 2 + 1); |
| if (!transStatus.isOk()) { |
| ALOGE("setMaxDequeuedBufferCount trans error: %s", transStatus.description().c_str()); |
| return false; |
| } |
| int32_t status; |
| if (!h2b(static_cast<HStatus>(transStatus), &status)) { |
| status = android::BAD_VALUE; |
| } |
| if (status != android::NO_ERROR) { |
| ALOGE("setMaxDequeuedBufferCount failed"); |
| return false; |
| } |
| |
| // Reset "buffer count in client". It will be accumulated in updateGraphicBlock() routine. |
| mBuffersInClient = 0; |
| |
| // Set allowAllocation to new producer. |
| Return<HStatus> transStatus2 = newProducer->allowAllocation(true); |
| if (!transStatus2.isOk()) { |
| ALOGE("allowAllocation(true) transaction error: %s", transStatus2.description().c_str()); |
| return false; |
| } |
| if (!h2b(static_cast<HStatus>(transStatus2), &status)) { |
| status = android::BAD_VALUE; |
| } |
| if (status != android::NO_ERROR) { |
| ALOGE("allowAllocation(true) failed"); |
| return false; |
| } |
| |
| // Fetch spare buffer slot from new producer first, this step also allows us to obtain the |
| // generation number and usage of new producer. While attaching buffers, generation number and |
| // usage must be aligned to the producer. |
| uint32_t newGeneration; |
| uint64_t newUsage; |
| c2_status_t err = fetchSpareBufferSlot(newProducer, mBufferFormat.mWidth, mBufferFormat.mHeight, |
| mBufferFormat.mPixelFormat, mBufferFormat.mUsage, |
| &newGeneration, &newUsage); |
| if (err != C2_OK) { |
| ALOGE("fetchSpareBufferSlot failed: %d", err); |
| return false; |
| } |
| |
| // Attach all buffers to new producer. |
| mProducerChangeSlotMap.clear(); |
| int32_t slot; |
| std::map<int32_t, std::shared_ptr<C2GraphicAllocation>> newSlotAllocations; |
| for (auto iter = mSlotAllocations.begin(); iter != mSlotAllocations.end(); ++iter) { |
| // Convert C2GraphicAllocation to GraphicBuffer. |
| uint32_t width, height, format, stride, igbp_slot, generation; |
| uint64_t usage, igbp_id; |
| android::_UnwrapNativeCodec2GrallocMetadata(iter->second->handle(), &width, &height, |
| &format, &usage, &stride, &generation, &igbp_id, |
| &igbp_slot); |
| native_handle_t* grallocHandle = |
| android::UnwrapNativeCodec2GrallocHandle(iter->second->handle()); |
| |
| // Update generation number and usage from newly-allocated spare buffer. |
| sp<GraphicBuffer> graphicBuffer = |
| new GraphicBuffer(grallocHandle, GraphicBuffer::CLONE_HANDLE, width, height, format, |
| 1, newUsage, stride); |
| if (graphicBuffer->initCheck() != android::NO_ERROR) { |
| ALOGE("Failed to create GraphicBuffer: %d", graphicBuffer->initCheck()); |
| return false; |
| } |
| graphicBuffer->setGenerationNumber(newGeneration); |
| native_handle_delete(grallocHandle); |
| |
| // Convert GraphicBuffer into HBuffer. |
| HBuffer hBuffer{}; |
| uint32_t hGenerationNumber{}; |
| if (!b2h(graphicBuffer, &hBuffer, &hGenerationNumber)) { |
| ALOGE("Failed to convert GraphicBuffer to HBuffer"); |
| return false; |
| } |
| |
| // Attach HBuffer to new producer and get the attached slot index. |
| bool converted{}; |
| Return<void> transStatus = newProducer->attachBuffer( |
| hBuffer, hGenerationNumber, |
| [&converted, &status, &slot](HStatus hStatus, int32_t hSlot, bool releaseAll) { |
| converted = h2b(hStatus, &status); |
| if (!converted) { |
| status = android::BAD_VALUE; |
| } |
| slot = hSlot; |
| if (converted && releaseAll && status == android::OK) { |
| status = android::INVALID_OPERATION; |
| } |
| }); |
| if (!transStatus.isOk()) { |
| ALOGE("attachBuffer trans error: %s", transStatus.description().c_str()); |
| return false; |
| } |
| if (status != android::NO_ERROR) { |
| ALOGE("attachBuffer failed: %d", status); |
| return false; |
| } |
| |
| // Convert back to C2GraphicAllocation wrapping new producer id, generation number, usage |
| // and slot index. |
| ALOGV("buffer wraps { producer id: %" PRIu64 ", slot: %d }", newProducerId, slot); |
| C2Handle* c2Handle = android::WrapNativeCodec2GrallocHandle( |
| graphicBuffer->handle, width, height, format, newUsage, stride, newGeneration, |
| newProducerId, slot); |
| if (!c2Handle) { |
| ALOGE("WrapNativeCodec2GrallocHandle failed"); |
| return false; |
| } |
| std::shared_ptr<C2GraphicAllocation> alloc; |
| c2_status_t err = mAllocator->priorGraphicAllocation(c2Handle, &alloc); |
| if (err != C2_OK) { |
| ALOGE("priorGraphicAllocation failed: %d", err); |
| return false; |
| } |
| |
| // Store to |newSlotAllocations| and also store old-to-new producer slot map. |
| ALOGV("Transfered buffer from old producer to new, slot prev: %d -> new %d", iter->first, |
| slot); |
| newSlotAllocations[slot] = std::move(alloc); |
| mProducerChangeSlotMap[iter->first] = slot; |
| } |
| |
| // Set allowAllocation to false so producer could not allocate new buffers. |
| Return<HStatus> transStatus4 = newProducer->allowAllocation(false); |
| if (!transStatus4.isOk()) { |
| ALOGE("allowAllocation(false) transaction error: %s", transStatus4.description().c_str()); |
| return false; |
| } |
| if (!h2b(static_cast<HStatus>(transStatus4), &status)) { |
| status = android::BAD_VALUE; |
| } |
| if (status != android::NO_ERROR) { |
| ALOGE("allowAllocation(false) failed"); |
| return false; |
| } |
| |
| // Try to detach all buffers from old producer. |
| for (const auto& slotAllocation : mSlotAllocations) { |
| Return<HStatus> transStatus = mProducer->detachBuffer(slotAllocation.first); |
| if (!transStatus.isOk()) { |
| ALOGE("detachBuffer trans error: %s", transStatus.description().c_str()); |
| return false; |
| } |
| if (!h2b(static_cast<HStatus>(transStatus), &status)) { |
| status = android::BAD_VALUE; |
| } |
| if (status != android::NO_ERROR) { |
| ALOGW("detachBuffer slot=%d from old producer failed: %d", slotAllocation.first, |
| status); |
| } |
| } |
| |
| mSlotAllocations = std::move(newSlotAllocations); |
| return true; |
| } |
| |
| c2_status_t C2VdaBqBlockPool::Impl::updateGraphicBlock( |
| bool willCancel, uint32_t oldSlot, uint32_t* newSlot, |
| std::shared_ptr<C2GraphicBlock>* block /* nonnull */) { |
| std::lock_guard<std::mutex> lock(mMutex); |
| |
| if (mProducerChangeSlotMap.empty()) { |
| ALOGD("A new buffer set is requested right after producer change, no more update needed."); |
| return C2_CANCELED; |
| } |
| |
| auto it = mProducerChangeSlotMap.find(static_cast<int32_t>(oldSlot)); |
| if (it == mProducerChangeSlotMap.end()) { |
| ALOGE("Cannot find old slot = %u in map...", oldSlot); |
| return C2_NOT_FOUND; |
| } |
| |
| int32_t slot = it->second; |
| *newSlot = static_cast<uint32_t>(slot); |
| mProducerChangeSlotMap.erase(it); |
| |
| if (willCancel) { |
| // The old C2GraphicBlock might be owned by client. Cancel this slot. |
| Return<HStatus> transStatus = mProducer->cancelBuffer(slot, hidl_handle{}); |
| if (!transStatus.isOk()) { |
| ALOGE("cancelBuffer transaction error: %s", transStatus.description().c_str()); |
| return C2_CORRUPTED; |
| } |
| // Client might try to attach the old buffer to the current producer on client's end, |
| // although it is useless for us anymore. However it will still occupy an available slot. |
| mBuffersInClient++; |
| } else { |
| // The old C2GraphicBlock is still owned by component, replace by the new one and keep this |
| // slot dequeued. |
| auto poolData = |
| std::make_shared<C2VdaBqBlockPoolData>(mProducerId, slot, shared_from_this()); |
| *block = _C2BlockFactory::CreateGraphicBlock(mSlotAllocations[slot], std::move(poolData)); |
| } |
| |
| if (mProducerChangeSlotMap.empty()) { |
| // The updateGraphicBlock() routine is about to finish. |
| // Set the correct maxDequeuedBufferCount to producer, which is "requested buffer count" + |
| // "buffer count in client" + 1 (spare buffer). |
| ALOGV("Requested buffer count: %zu, buffer count in client: %u", mSlotAllocations.size(), |
| mBuffersInClient); |
| Return<HStatus> transStatus = mProducer->setMaxDequeuedBufferCount(mSlotAllocations.size() + |
| mBuffersInClient + 1); |
| if (!transStatus.isOk()) { |
| ALOGE("setMaxDequeuedBufferCount trans error: %s", transStatus.description().c_str()); |
| return C2_CORRUPTED; |
| } |
| int32_t status; |
| if (!h2b(static_cast<HStatus>(transStatus), &status)) { |
| status = android::BAD_VALUE; |
| } |
| if (status != android::NO_ERROR) { |
| ALOGE("setMaxDequeuedBufferCount failed: %d", status); |
| return C2_CORRUPTED; |
| } |
| mProducerSwitched = true; |
| } |
| |
| return C2_OK; |
| } |
| |
| c2_status_t C2VdaBqBlockPool::Impl::getMinBuffersForDisplay(size_t* bufferCount) { |
| std::lock_guard<std::mutex> lock(mMutex); |
| if (!mProducer) { |
| ALOGD("No HGraphicBufferProducer is configured..."); |
| return C2_NO_INIT; |
| } |
| |
| int32_t status, value; |
| Return<void> transStatus = mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, |
| [&status, &value](int32_t tStatus, int32_t tValue) { |
| status = tStatus; |
| value = tValue; |
| }); |
| if (!transStatus.isOk()) { |
| ALOGE("query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS) trans error: %s", |
| transStatus.description().c_str()); |
| return C2_CORRUPTED; |
| } |
| if (status != android::NO_ERROR) { |
| ALOGE("query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS) failed: %d", status); |
| return asC2Error(status); |
| } |
| if (value <= 0) { |
| ALOGE("Illegal value of NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS = %d", value); |
| return C2_BAD_VALUE; |
| } |
| *bufferCount = static_cast<size_t>(value); |
| return C2_OK; |
| } |
| |
| void C2VdaBqBlockPool::Impl::detachBuffer(uint64_t producerId, int32_t slotId) { |
| ALOGV("detachBuffer: producer id = %" PRIu64 ", slot = %d", producerId, slotId); |
| std::lock_guard<std::mutex> lock(mMutex); |
| if (producerId == mProducerId && mProducer) { |
| Return<HStatus> transStatus = mProducer->detachBuffer(slotId); |
| if (!transStatus.isOk()) { |
| ALOGE("detachBuffer trans error: %s", transStatus.description().c_str()); |
| return; |
| } |
| int32_t status; |
| if (!h2b(static_cast<HStatus>(transStatus), &status)) { |
| status = android::BAD_VALUE; |
| } |
| if (status != android::NO_ERROR) { |
| ALOGD("detachBuffer failed: %d", status); |
| return; |
| } |
| |
| auto it = mSlotAllocations.find(slotId); |
| // It may happen that the slot is not included in |mSlotAllocations|, which means it is |
| // released after resolution change. |
| if (it != mSlotAllocations.end()) { |
| mSlotAllocations.erase(it); |
| } |
| } |
| } |
| |
| C2VdaBqBlockPool::C2VdaBqBlockPool(const std::shared_ptr<C2Allocator>& allocator, |
| const local_id_t localId) |
| : C2BufferQueueBlockPool(allocator, localId), mLocalId(localId), mImpl(new Impl(allocator)) {} |
| |
| c2_status_t C2VdaBqBlockPool::fetchGraphicBlock( |
| uint32_t width, uint32_t height, uint32_t format, C2MemoryUsage usage, |
| std::shared_ptr<C2GraphicBlock>* block /* nonnull */) { |
| if (mImpl) { |
| return mImpl->fetchGraphicBlock(width, height, format, usage, block); |
| } |
| return C2_NO_INIT; |
| } |
| |
| void C2VdaBqBlockPool::setRenderCallback( |
| const C2BufferQueueBlockPool::OnRenderCallback& renderCallback) { |
| if (mImpl) { |
| mImpl->setRenderCallback(renderCallback); |
| } |
| } |
| |
| c2_status_t C2VdaBqBlockPool::requestNewBufferSet(int32_t bufferCount) { |
| if (mImpl) { |
| return mImpl->requestNewBufferSet(bufferCount); |
| } |
| return C2_NO_INIT; |
| } |
| |
| void C2VdaBqBlockPool::configureProducer(const sp<HGraphicBufferProducer>& producer) { |
| if (mImpl) { |
| mImpl->configureProducer(producer); |
| } |
| } |
| |
| c2_status_t C2VdaBqBlockPool::updateGraphicBlock( |
| bool willCancel, uint32_t oldSlot, uint32_t* newSlot, |
| std::shared_ptr<C2GraphicBlock>* block /* nonnull */) { |
| if (mImpl) { |
| return mImpl->updateGraphicBlock(willCancel, oldSlot, newSlot, block); |
| } |
| return C2_NO_INIT; |
| } |
| |
| c2_status_t C2VdaBqBlockPool::getMinBuffersForDisplay(size_t* bufferCount) { |
| if (mImpl) { |
| return mImpl->getMinBuffersForDisplay(bufferCount); |
| } |
| return C2_NO_INIT; |
| } |
| |
| C2VdaBqBlockPoolData::C2VdaBqBlockPoolData(uint64_t producerId, int32_t slotId, |
| const std::shared_ptr<C2VdaBqBlockPool::Impl>& pool) |
| : mProducerId(producerId), mSlotId(slotId), mPool(pool) {} |
| |
| C2VdaBqBlockPoolData::~C2VdaBqBlockPoolData() { |
| if (mShared || !mPool) { |
| return; |
| } |
| mPool->detachBuffer(mProducerId, mSlotId); |
| } |
| |
| } // namespace android |