blob: 035134b07bec3e4faec74e190a4642d2bbf058b9 [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 <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