blob: 9abc6986970130a004191e0045acdfaa0152b191 [file] [log] [blame]
// 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 <thread>
#include <C2AllocatorGralloc.h>
#include <C2BlockInternal.h>
#include <android/hardware/graphics/bufferqueue/2.0/IGraphicBufferProducer.h>
#include <android/hardware/graphics/bufferqueue/2.0/IProducerListener.h>
#include <base/callback.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 limit of acquiring lock of timed_mutex in milliseconds.
constexpr std::chrono::milliseconds kTimedMutexTimeoutMs = std::chrono::milliseconds(500);
} // namespace
using namespace std::chrono_literals;
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::BufferQueueDefs::RELEASE_ALL_BUFFERS;
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 HGraphicBufferProducer =
::android::hardware::graphics::bufferqueue::V2_0::IGraphicBufferProducer;
using HProducerListener = ::android::hardware::graphics::bufferqueue::V2_0::IProducerListener;
using HConnectionType = hardware::graphics::bufferqueue::V2_0::ConnectionType;
using HQueueBufferOutput =
::android::hardware::graphics::bufferqueue::V2_0::IGraphicBufferProducer::QueueBufferOutput;
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;
}
class H2BGraphicBufferProducer {
public:
explicit H2BGraphicBufferProducer(sp<HGraphicBufferProducer> base) : mBase(base) {}
~H2BGraphicBufferProducer() = default;
status_t requestBuffer(int slot, sp<GraphicBuffer>* buf) {
bool converted = false;
status_t status = UNKNOWN_ERROR;
Return<void> transResult = mBase->requestBuffer(
slot, [&converted, &status, buf](HStatus hStatus, HBuffer const& hBuffer,
uint32_t generationNumber) {
converted = h2b(hStatus, &status) && h2b(hBuffer, buf);
if (*buf) {
(*buf)->setGenerationNumber(generationNumber);
}
});
if (!transResult.isOk()) {
ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str());
return FAILED_TRANSACTION;
}
if (!converted) {
ALOGE("%s(): corrupted transaction.", __func__);
return FAILED_TRANSACTION;
}
if (status != android::NO_ERROR) {
ALOGE("%s() failed: %d", __func__, status);
}
return status;
}
status_t setMaxDequeuedBufferCount(int maxDequeuedBuffers) {
status_t status = UNKNOWN_ERROR;
Return<HStatus> transResult =
mBase->setMaxDequeuedBufferCount(static_cast<int32_t>(maxDequeuedBuffers));
if (!transResult.isOk()) {
ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str());
return FAILED_TRANSACTION;
}
if (!h2b(static_cast<HStatus>(transResult), &status)) {
ALOGE("%s(): corrupted transaction.", __func__);
return FAILED_TRANSACTION;
}
if (status != android::NO_ERROR) {
ALOGE("%s() failed: %d", __func__, status);
}
return status;
}
status_t dequeueBuffer(uint32_t width, uint32_t height, uint32_t pixelFormat,
C2AndroidMemoryUsage androidUsage, int* slot, sp<Fence>* fence) {
using Input = HGraphicBufferProducer::DequeueBufferInput;
using Output = HGraphicBufferProducer::DequeueBufferOutput;
Input input{width, height, pixelFormat, androidUsage.asGrallocUsage()};
bool converted = false;
status_t status = UNKNOWN_ERROR;
Return<void> transResult = mBase->dequeueBuffer(
input, [&converted, &status, &slot, &fence](HStatus hStatus, int32_t hSlot,
Output const& hOutput) {
converted = h2b(hStatus, &status);
if (!converted || status != android::NO_ERROR) {
return;
}
*slot = hSlot;
if (hOutput.bufferNeedsReallocation) {
status = BUFFER_NEEDS_REALLOCATION;
}
converted = h2b(hOutput.fence, fence);
});
if (!transResult.isOk()) {
ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str());
return FAILED_TRANSACTION;
}
if (!converted) {
ALOGE("%s(): corrupted transaction.", __func__);
return FAILED_TRANSACTION;
}
if (status != android::NO_ERROR && status != BUFFER_NEEDS_REALLOCATION &&
status != android::TIMED_OUT) {
ALOGE("%s() failed: %d", __func__, status);
}
return status;
}
status_t detachBuffer(int slot) {
status_t status = UNKNOWN_ERROR;
Return<HStatus> transResult = mBase->detachBuffer(static_cast<int32_t>(slot));
if (!transResult.isOk()) {
ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str());
return FAILED_TRANSACTION;
}
if (!h2b(static_cast<HStatus>(transResult), &status)) {
ALOGE("%s(): corrupted transaction.", __func__);
return FAILED_TRANSACTION;
}
if (status != android::NO_ERROR) {
ALOGE("%s() failed: %d", __func__, status);
}
return status;
}
status_t attachBuffer(const sp<GraphicBuffer>& buffer, int* outSlot) {
HBuffer hBuffer;
uint32_t hGenerationNumber;
if (!b2h(buffer, &hBuffer, &hGenerationNumber)) {
ALOGE("%s: invalid input buffer.", __func__);
return BAD_VALUE;
}
bool converted = false;
status_t status = UNKNOWN_ERROR;
Return<void> transResult = mBase->attachBuffer(
hBuffer, hGenerationNumber,
[&converted, &status, outSlot](HStatus hStatus, int32_t hSlot,
bool releaseAllBuffers) {
converted = h2b(hStatus, &status);
*outSlot = static_cast<int>(hSlot);
if (converted && releaseAllBuffers && status == android::NO_ERROR) {
status = RELEASE_ALL_BUFFERS;
}
});
if (!transResult.isOk()) {
ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str());
return FAILED_TRANSACTION;
}
if (!converted) {
ALOGE("%s(): corrupted transaction.", __func__);
return FAILED_TRANSACTION;
}
if (status != android::NO_ERROR) {
ALOGE("%s() failed: %d", __func__, status);
}
return status;
}
status_t cancelBuffer(int slot, const sp<Fence>& fence) {
HFenceWrapper hFenceWrapper;
if (!b2h(fence, &hFenceWrapper)) {
ALOGE("%s(): corrupted input fence.", __func__);
return UNKNOWN_ERROR;
}
status_t status = UNKNOWN_ERROR;
Return<HStatus> transResult =
mBase->cancelBuffer(static_cast<int32_t>(slot), hFenceWrapper.getHandle());
if (!transResult.isOk()) {
ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str());
return FAILED_TRANSACTION;
}
if (!h2b(static_cast<HStatus>(transResult), &status)) {
ALOGE("%s(): corrupted transaction.", __func__);
return FAILED_TRANSACTION;
}
if (status != android::NO_ERROR) {
ALOGE("%s() failed: %d", __func__, status);
}
return status;
}
int query(int what, int* value) {
int result = 0;
Return<void> transResult =
mBase->query(static_cast<int32_t>(what), [&result, value](int32_t r, int32_t v) {
result = static_cast<int>(r);
*value = static_cast<int>(v);
});
if (!transResult.isOk()) {
ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str());
return FAILED_TRANSACTION;
}
return result;
}
status_t allowAllocation(bool allow) {
status_t status = UNKNOWN_ERROR;
Return<HStatus> transResult = mBase->allowAllocation(allow);
if (!transResult.isOk()) {
ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str());
return FAILED_TRANSACTION;
}
if (!h2b(static_cast<HStatus>(transResult), &status)) {
ALOGE("%s(): corrupted transaction.", __func__);
return FAILED_TRANSACTION;
}
if (status != android::NO_ERROR) {
ALOGW("%s() failed: %d", __func__, status);
}
return status;
}
status_t getUniqueId(uint64_t* outId) const {
Return<uint64_t> transResult = mBase->getUniqueId();
if (!transResult.isOk()) {
ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str());
return FAILED_TRANSACTION;
}
*outId = static_cast<uint64_t>(transResult);
return android::NO_ERROR;
}
// android::IProducerListener cannot be depended by vendor library, so we use HProducerListener
// directly.
status_t connect(sp<HProducerListener> const& hListener, int32_t api,
bool producerControlledByApp) {
bool converted = false;
status_t status = UNKNOWN_ERROR;
// hack(b/146409777): We pass self-defined api, so we don't use b2h() here.
Return<void> transResult = mBase->connect(
hListener, static_cast<HConnectionType>(api), producerControlledByApp,
[&converted, &status](HStatus hStatus, HQueueBufferOutput const& /* hOutput */) {
converted = h2b(hStatus, &status);
});
if (!transResult.isOk()) {
ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str());
return FAILED_TRANSACTION;
}
if (!converted) {
ALOGE("%s(): corrupted transaction.", __func__);
return FAILED_TRANSACTION;
}
return status;
}
status_t setDequeueTimeout(nsecs_t timeout) {
status_t status = UNKNOWN_ERROR;
Return<HStatus> transResult = mBase->setDequeueTimeout(static_cast<int64_t>(timeout));
if (!transResult.isOk()) {
ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str());
return FAILED_TRANSACTION;
}
if (!h2b(static_cast<HStatus>(transResult), &status)) {
ALOGE("%s(): corrupted transaction.", __func__);
return FAILED_TRANSACTION;
}
return status;
}
private:
const sp<HGraphicBufferProducer> mBase;
};
// This class is used to notify the listener when a certain event happens.
class EventNotifier : public virtual android::RefBase {
public:
class Listener {
public:
virtual ~Listener() = default;
// Called by EventNotifier when a certain event happens.
virtual void onEventNotified() = 0;
};
explicit EventNotifier(const std::shared_ptr<Listener>& listener) : mListener(listener) {}
virtual ~EventNotifier() = default;
protected:
void notify() {
ALOGV("%s()", __func__);
std::shared_ptr<Listener> listener = mListener.lock();
if (listener) {
listener->onEventNotified();
}
}
std::weak_ptr<Listener> mListener;
};
// Notifies the listener when the connected IGBP releases buffers.
class BufferReleasedNotifier : public EventNotifier, public HProducerListener {
public:
using EventNotifier::EventNotifier;
~BufferReleasedNotifier() override = default;
// HProducerListener implementation
Return<void> onBuffersReleased(uint32_t count) override {
ALOGV("%s(%u)", __func__, count);
if (count > 0) {
notify();
}
return {};
}
};
/**
* 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 EventNotifier::Listener {
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;
// EventNotifier::Listener implementation.
void onEventNotified() override;
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);
bool setNotifyBlockAvailableCb(::base::OnceClosure cb);
private:
friend struct C2VdaBqBlockPoolData;
// 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);
// Queries the generation and usage flags from the given producer by dequeuing and requesting a
// buffer (the buffer is then detached and freed).
c2_status_t queryGenerationAndUsage(H2BGraphicBufferProducer* const producer, uint32_t width,
uint32_t height, uint32_t pixelFormat,
C2AndroidMemoryUsage androidUsage, uint32_t* generation,
uint64_t* usage);
// Switches producer and transfers allocated buffers from old producer to the new one.
bool switchProducer(H2BGraphicBufferProducer* const newProducer, uint64_t newProducerId);
const std::shared_ptr<C2Allocator> mAllocator;
std::unique_ptr<H2BGraphicBufferProducer> 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;
// 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 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;
// Listener for buffer release events.
sp<EventNotifier> mFetchBufferNotifier;
std::mutex mBufferReleaseMutex;
// Set to true when the buffer release event is triggered after dequeueing
// buffer from IGBP times out.
bool mBufferReleasedAfterTimedOut GUARDED_BY(mBufferReleaseMutex) = false;
// The callback to notify the caller the buffer is available.
::base::OnceClosure mNotifyBlockAvailableCb GUARDED_BY(mBufferReleaseMutex);
};
C2VdaBqBlockPool::Impl::Impl(const std::shared_ptr<C2Allocator>& allocator)
: mAllocator(allocator),
mAllocateBuffersLock(mConfigureProducerAndAllocateBuffersMutex, std::defer_lock),
mBuffersRequested(0u) {}
c2_status_t C2VdaBqBlockPool::Impl::fetchGraphicBlock(
uint32_t width, uint32_t height, uint32_t format, C2MemoryUsage usage,
std::shared_ptr<C2GraphicBlock>* block /* nonnull */) {
ALOGV("%s()", __func__);
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;
}
C2AndroidMemoryUsage androidUsage = usage;
uint32_t pixelFormat = format;
int32_t slot;
sp<Fence> fence = new Fence();
status_t status =
mProducer->dequeueBuffer(width, height, pixelFormat, androidUsage, &slot, &fence);
// The C2VdaBqBlockPool does not fully own the bufferqueue. After buffers are dequeued here,
// they are passed into the codec2 framework, processed, and eventually queued into the
// bufferqueue. The C2VdaBqBlockPool cannot determine exactly when a buffer gets queued.
// However, if every buffer is being processed by the codec2 framework, then dequeueBuffer()
// will return INVALID_OPERATION because of an attempt to dequeue too many buffers.
// The C2VdaBqBlockPool cannot prevent this from happening, so just map it to TIMED_OUT
// and let the C2VdaBqBlockPool's caller's timeout retry logic handle the failure.
if (status == android::INVALID_OPERATION) {
status = android::TIMED_OUT;
}
if (status == android::TIMED_OUT) {
std::lock_guard<std::mutex> lock(mBufferReleaseMutex);
mBufferReleasedAfterTimedOut = false;
}
if (status != android::NO_ERROR && status != BUFFER_NEEDS_REALLOCATION) {
return asC2Error(status);
}
// Wait for acquire fence if we get one.
if (fence) {
status_t fenceStatus = fence->wait(kFenceWaitTimeMs);
if (fenceStatus != android::NO_ERROR) {
if (mProducer->cancelBuffer(slot, fence) != android::NO_ERROR) {
return C2_CORRUPTED;
}
if (fenceStatus == -ETIME) { // fence wait timed out
ALOGV("%s(): buffer (slot=%d) fence wait timed out", __func__, slot);
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 (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.");
if (mProducer->detachBuffer(slot) != android::NO_ERROR) {
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();
status = mProducer->requestBuffer(slot, &slotBuffer);
if (status != android::NO_ERROR) {
if (mProducer->cancelBuffer(slot, fence) != android::NO_ERROR) {
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) {
// Already allocated enough buffers, set allowAllocation to false to restrict the
// eligible slots to allocated ones for future dequeue.
status = mProducer->allowAllocation(false);
if (status != android::NO_ERROR) {
return asC2Error(status);
}
// Store buffer formats for future usage.
mBufferFormat = BufferFormat(width, height, pixelFormat, androidUsage);
ALOG_ASSERT(mAllocateBuffersLock.owns_lock());
mAllocateBuffersLock.unlock();
}
}
auto poolData = std::make_shared<C2VdaBqBlockPoolData>(mProducerId, slot, shared_from_this());
*block = _C2BlockFactory::CreateGraphicBlock(mSlotAllocations[slot], std::move(poolData));
return C2_OK;
}
void C2VdaBqBlockPool::Impl::onEventNotified() {
ALOGV("%s()", __func__);
::base::OnceClosure outputCb;
{
std::lock_guard<std::mutex> lock(mBufferReleaseMutex);
mBufferReleasedAfterTimedOut = true;
if (mNotifyBlockAvailableCb) {
outputCb = std::move(mNotifyBlockAvailableCb);
}
}
// Calling the callback outside the lock to avoid the deadlock.
if (outputCb) {
std::move(outputCb).Run();
}
}
c2_status_t C2VdaBqBlockPool::Impl::queryGenerationAndUsage(
H2BGraphicBufferProducer* const producer, uint32_t width, uint32_t height,
uint32_t pixelFormat, C2AndroidMemoryUsage androidUsage, uint32_t* generation,
uint64_t* usage) {
ALOGV("queryGenerationAndUsage");
sp<Fence> fence = new Fence();
int32_t status;
int32_t slot;
status = producer->dequeueBuffer(width, height, pixelFormat, androidUsage, &slot, &fence);
if (status != android::NO_ERROR && status != BUFFER_NEEDS_REALLOCATION) {
return asC2Error(status);
}
// Wait for acquire fence if we get one.
if (fence) {
status_t fenceStatus = fence->wait(kFenceWaitTimeMs);
if (fenceStatus != android::NO_ERROR) {
if (producer->cancelBuffer(slot, fence) != android::NO_ERROR) {
return C2_CORRUPTED;
}
if (fenceStatus == -ETIME) { // fence wait timed out
ALOGV("%s(): buffer (slot=%d) fence wait timed out", __func__, slot);
return C2_TIMED_OUT;
}
ALOGE("buffer fence wait error: %d", fenceStatus);
return asC2Error(fenceStatus);
}
}
// Call requestBuffer to allocate buffer for the slot and obtain the reference.
// Get generation number here.
sp<GraphicBuffer> slotBuffer = new GraphicBuffer();
status = producer->requestBuffer(slot, &slotBuffer);
// Detach and delete the temporary buffer.
if (producer->detachBuffer(slot) != android::NO_ERROR) {
return C2_CORRUPTED;
}
// Check requestBuffer return flag.
if (status != android::NO_ERROR) {
return asC2Error(status);
}
// Get generation number and usage from the slot buffer.
*usage = slotBuffer->getUsage();
*generation = slotBuffer->getGenerationNumber();
ALOGV("Obtained from temp buffer: generation = %u, usage = %" PRIu64 "", *generation, *usage);
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;
}
status_t status = mProducer->detachBuffer(slot);
if (status == android::NO_INIT) {
// No more active buffer slot. Break the loop now.
break;
} else if (status != android::NO_ERROR) {
return C2_CORRUPTED;
}
}
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.
status_t status = mProducer->setMaxDequeuedBufferCount(bufferCount + mSlotAllocations.size());
if (status != android::NO_ERROR) {
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);
status = mProducer->allowAllocation(true);
if (status != android::NO_ERROR) {
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);
auto newProducer = std::make_unique<H2BGraphicBufferProducer>(producer);
uint64_t producerId;
if (newProducer->getUniqueId(&producerId) != android::NO_ERROR) {
return;
}
if (mProducer && mProducerId != producerId) {
ALOGI("Producer (Surface) is going to switch... ( %" PRIu64 " -> %" PRIu64 " )",
mProducerId, producerId);
if (!switchProducer(newProducer.get(), producerId)) {
mProducerChangeSlotMap.clear();
return;
}
} else {
mSlotAllocations.clear();
}
if (newProducer->setDequeueTimeout(0) != android::NO_ERROR) {
ALOGE("%s(): failed to setDequeueTimeout(0)", __func__);
return;
}
// hack(b/146409777): Try to connect ARC-specific listener first.
sp<BufferReleasedNotifier> listener = new BufferReleasedNotifier(shared_from_this());
if (newProducer->connect(listener, 'ARC\0', false) == android::NO_ERROR) {
ALOGI("connected to ARC-specific IGBP listener.");
mFetchBufferNotifier = listener;
}
// 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 = std::move(newProducer);
mProducerId = producerId;
}
bool C2VdaBqBlockPool::Impl::switchProducer(H2BGraphicBufferProducer* 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" to make sure it has enough available slots to request
// buffers 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.
if (newProducer->setMaxDequeuedBufferCount(mSlotAllocations.size() * 2) != android::NO_ERROR) {
return false;
}
// Reset "buffer count in client". It will be accumulated in updateGraphicBlock() routine.
mBuffersInClient = 0;
// Set allowAllocation to new producer.
if (newProducer->allowAllocation(true) != android::NO_ERROR) {
return false;
}
// Get a buffer from the new producer to get 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 = queryGenerationAndUsage(newProducer, mBufferFormat.mWidth,
mBufferFormat.mHeight, mBufferFormat.mPixelFormat,
mBufferFormat.mUsage, &newGeneration, &newUsage);
if (err != C2_OK) {
ALOGE("queryGenerationAndUsage 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.
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);
if (newProducer->attachBuffer(graphicBuffer, &slot) != android::NO_ERROR) {
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.
if (newProducer->allowAllocation(false) != android::NO_ERROR) {
ALOGE("allowAllocation(false) failed");
return false;
}
// Try to detach all buffers from old producer.
for (const auto& slotAllocation : mSlotAllocations) {
status_t status = mProducer->detachBuffer(slotAllocation.first);
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) {
sp<Fence> fence = new Fence();
// The old C2GraphicBlock might be owned by client. Cancel this slot.
if (mProducer->cancelBuffer(slot, fence) != android::NO_ERROR) {
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".
ALOGV("Requested buffer count: %zu, buffer count in client: %u", mSlotAllocations.size(),
mBuffersInClient);
if (mProducer->setMaxDequeuedBufferCount(mSlotAllocations.size() + mBuffersInClient) !=
android::NO_ERROR) {
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;
status = mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &value);
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) {
if (mProducer->detachBuffer(slotId) != android::NO_ERROR) {
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);
}
}
}
bool C2VdaBqBlockPool::Impl::setNotifyBlockAvailableCb(::base::OnceClosure cb) {
ALOGV("%s()", __func__);
if (mFetchBufferNotifier == nullptr) {
return false;
}
::base::OnceClosure outputCb;
{
std::lock_guard<std::mutex> lock(mBufferReleaseMutex);
// If there is any buffer released after dequeueBuffer() timed out, then we could notify the
// caller directly.
if (mBufferReleasedAfterTimedOut) {
outputCb = std::move(cb);
} else {
mNotifyBlockAvailableCb = std::move(cb);
}
}
// Calling the callback outside the lock to avoid the deadlock.
if (outputCb) {
std::move(outputCb).Run();
}
return true;
}
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;
}
bool C2VdaBqBlockPool::setNotifyBlockAvailableCb(::base::OnceClosure cb) {
if (mImpl) {
return mImpl->setNotifyBlockAvailableCb(std::move(cb));
}
return false;
}
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