blob: d53f4a01c94fc38d902c5dbdc6ba3d8bf01d957d [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 <string.h>
#include <chrono>
#include <mutex>
#include <set>
#include <sstream>
#include <thread>
#include <C2AllocatorGralloc.h>
#include <C2BlockInternal.h>
#include <C2SurfaceSyncObj.h>
#include <android/hardware/graphics/bufferqueue/2.0/IProducerListener.h>
#include <base/callback.h>
#include <log/log.h>
#include <ui/BufferQueueDefs.h>
#include <v4l2_codec2/plugin_store/DmabufHelpers.h>
#include <v4l2_codec2/plugin_store/H2BGraphicBufferProducer.h>
#include <v4l2_codec2/plugin_store/V4L2AllocatorId.h>
namespace android {
namespace {
// The wait time for acquire fence in milliseconds. The normal display is 60Hz,
// which period is 16ms. We choose 2x period as timeout.
constexpr int kFenceWaitTimeMs = 32;
// The default maximum dequeued buffer count of IGBP. Currently we don't use
// this value to restrict the count of allocated buffers, so we choose a huge
// enough value here.
constexpr int kMaxDequeuedBufferCount = 32u;
} // namespace
using namespace std::chrono_literals;
// Type for IGBP slot index.
using slot_t = int32_t;
using ::android::BufferQueueDefs::BUFFER_NEEDS_REALLOCATION;
using ::android::BufferQueueDefs::NUM_BUFFER_SLOTS;
using ::android::hardware::Return;
using HProducerListener = ::android::hardware::graphics::bufferqueue::V2_0::IProducerListener;
static c2_status_t asC2Error(status_t err) {
switch (err) {
case OK:
return C2_OK;
case NO_INIT:
return C2_NO_INIT;
case BAD_VALUE:
return C2_BAD_VALUE;
case TIMED_OUT:
return C2_TIMED_OUT;
case WOULD_BLOCK:
return C2_BLOCKING;
case NO_MEMORY:
return C2_NO_MEMORY;
}
return C2_CORRUPTED;
}
// Convert GraphicBuffer to C2GraphicAllocation and wrap producer id and slot index.
std::shared_ptr<C2GraphicAllocation> ConvertGraphicBuffer2C2Allocation(
sp<GraphicBuffer> graphicBuffer, const uint64_t igbpId, const slot_t slot,
C2Allocator* const allocator) {
ALOGV("%s(idbpId=0x%" PRIx64 ", slot=%d)", __func__, igbpId, slot);
C2Handle* c2Handle = WrapNativeCodec2GrallocHandle(
graphicBuffer->handle, graphicBuffer->width, graphicBuffer->height,
graphicBuffer->format, graphicBuffer->usage, graphicBuffer->stride,
graphicBuffer->getGenerationNumber(), igbpId, slot);
if (!c2Handle) {
ALOGE("WrapNativeCodec2GrallocHandle() failed");
return nullptr;
}
std::shared_ptr<C2GraphicAllocation> allocation;
const auto err = allocator->priorGraphicAllocation(c2Handle, &allocation);
if (err != C2_OK) {
ALOGE("C2Allocator::priorGraphicAllocation() failed: %d", err);
native_handle_close(c2Handle);
native_handle_delete(c2Handle);
return nullptr;
}
return allocation;
}
// 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(std::weak_ptr<Listener> listener) : mListener(std::move(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 {};
}
};
// IGBP expects its user (e.g. C2VdaBqBlockPool) to keep the mapping from dequeued slot index to
// graphic buffers. Also, C2VdaBqBlockPool guaratees to fetch N fixed set of buffers with buffer
// identifier. So this class stores the mapping from slot index to buffers and the mapping from
// buffer unique ID to buffers.
// This class also implements functionalities for buffer migration when surface switching. Buffers
// are owned by either component (i.e. local buffers) or CCodec framework (i.e. remote buffers).
// When switching surface, the ccodec framework migrates remote buffers to the new surfaces. Then
// C2VdaBqBlockPool migrates local buffers. However, some buffers might be lost during migration.
// We assume that there are enough buffers migrated to the new surface to continue the playback.
// After |NUM_BUFFER_SLOTS| amount of buffers are dequeued from new surface, all buffers should
// be dequeued at least once. Then we treat the missing buffer as lost, and attach these bufers to
// the new surface.
class TrackedGraphicBuffers {
public:
using value_type = std::tuple<slot_t, unique_id_t, std::shared_ptr<C2GraphicAllocation>>;
TrackedGraphicBuffers() = default;
~TrackedGraphicBuffers() = default;
void reset() {
mSlotId2GraphicBuffer.clear();
mSlotId2PoolData.clear();
mAllocationsRegistered.clear();
mAllocationsToBeMigrated.clear();
mMigrateLostBufferCounter = 0;
mGenerationToBeMigrated = 0;
}
void registerUniqueId(unique_id_t uniqueId, std::shared_ptr<C2GraphicAllocation> allocation) {
ALOGV("%s(uniqueId=%u)", __func__, uniqueId);
ALOG_ASSERT(allocation != nullptr);
mAllocationsRegistered[uniqueId] = std::move(allocation);
}
std::shared_ptr<C2GraphicAllocation> getRegisteredAllocation(unique_id_t uniqueId) {
const auto iter = mAllocationsRegistered.find(uniqueId);
ALOG_ASSERT(iter != mAllocationsRegistered.end());
return iter->second;
}
bool hasUniqueId(unique_id_t uniqueId) const {
return mAllocationsRegistered.find(uniqueId) != mAllocationsRegistered.end() ||
mAllocationsToBeMigrated.find(uniqueId) != mAllocationsToBeMigrated.end();
}
void updateSlotBuffer(slot_t slotId, unique_id_t uniqueId, sp<GraphicBuffer> slotBuffer) {
ALOGV("%s(slotId=%d)", __func__, slotId);
ALOG_ASSERT(slotBuffer != nullptr);
mSlotId2GraphicBuffer[slotId] = std::make_pair(uniqueId, std::move(slotBuffer));
}
std::pair<unique_id_t, sp<GraphicBuffer>> getSlotBuffer(slot_t slotId) const {
const auto iter = mSlotId2GraphicBuffer.find(slotId);
ALOG_ASSERT(iter != mSlotId2GraphicBuffer.end());
return iter->second;
}
bool hasSlotId(slot_t slotId) const {
return mSlotId2GraphicBuffer.find(slotId) != mSlotId2GraphicBuffer.end();
}
void updatePoolData(slot_t slotId, std::weak_ptr<C2BufferQueueBlockPoolData> poolData) {
ALOGV("%s(slotId=%d)", __func__, slotId);
ALOG_ASSERT(hasSlotId(slotId));
mSlotId2PoolData[slotId] = std::move(poolData);
}
bool migrateLocalBuffers(H2BGraphicBufferProducer* const producer, uint64_t producerId,
uint32_t generation, uint64_t usage) {
ALOGV("%s(producerId=%" PRIx64 ", generation=%u, usage=%" PRIx64 ")", __func__, producerId,
generation, usage);
mGenerationToBeMigrated = generation;
mUsageToBeMigrated = usage;
// Move all buffers to mAllocationsToBeMigrated.
for (auto& pair : mAllocationsRegistered) {
if (!mAllocationsToBeMigrated.insert(pair).second) {
ALOGE("%s() duplicated uniqueId=%u", __func__, pair.first);
return false;
}
}
mAllocationsRegistered.clear();
ALOGV("%s(producerId=%" PRIx64 ", generation=%u, usage=%" PRIx64 ") before %s", __func__,
producerId, generation, usage, debugString().c_str());
// Migrate local buffers.
std::map<slot_t, std::pair<unique_id_t, sp<GraphicBuffer>>> newSlotId2GraphicBuffer;
std::map<slot_t, std::weak_ptr<C2BufferQueueBlockPoolData>> newSlotId2PoolData;
for (const auto& pair : mSlotId2PoolData) {
auto oldSlot = pair.first;
auto poolData = pair.second.lock();
if (!poolData) {
continue;
}
unique_id_t uniqueId;
sp<GraphicBuffer> slotBuffer;
std::shared_ptr<C2SurfaceSyncMemory> syncMem;
std::tie(uniqueId, slotBuffer) = getSlotBuffer(oldSlot);
slot_t newSlot = poolData->migrate(producer->getBase(), mGenerationToBeMigrated,
mUsageToBeMigrated, producerId, slotBuffer,
slotBuffer->getGenerationNumber(),
syncMem);
if (newSlot < 0) {
ALOGW("%s() Failed to migrate local buffer: uniqueId=%u, oldSlot=%d", __func__,
uniqueId, oldSlot);
continue;
}
ALOGV("%s() migrated buffer: uniqueId=%u, oldSlot=%d, newSlot=%d", __func__, uniqueId,
oldSlot, newSlot);
newSlotId2GraphicBuffer[newSlot] = std::make_pair(uniqueId, std::move(slotBuffer));
newSlotId2PoolData[newSlot] = std::move(poolData);
if (!moveBufferToRegistered(uniqueId)) {
ALOGE("%s() failed to move buffer to registered, uniqueId=%u", __func__, uniqueId);
return false;
}
}
mSlotId2GraphicBuffer = std::move(newSlotId2GraphicBuffer);
mSlotId2PoolData = std::move(newSlotId2PoolData);
// Choose a big enough number to ensure all buffer should be dequeued at least once.
mMigrateLostBufferCounter = NUM_BUFFER_SLOTS;
ALOGD("%s() migrated %zu local buffers", __func__, mAllocationsRegistered.size());
return true;
}
bool needMigrateLostBuffers() const {
return mMigrateLostBufferCounter == 0 && !mAllocationsToBeMigrated.empty();
}
status_t migrateLostBuffer(C2Allocator* const allocator,
H2BGraphicBufferProducer* const producer, const uint64_t producerId,
slot_t* newSlot) {
ALOGV("%s() %s", __func__, debugString().c_str());
if (!needMigrateLostBuffers()) {
return NO_INIT;
}
auto iter = mAllocationsToBeMigrated.begin();
const unique_id_t uniqueId = iter->first;
const C2Handle* c2Handle = iter->second->handle();
// Convert C2GraphicAllocation to GraphicBuffer, and update generation and usage.
uint32_t width, height, format, stride, igbpSlot, generation;
uint64_t usage, igbpId;
_UnwrapNativeCodec2GrallocMetadata(c2Handle, &width, &height, &format, &usage, &stride,
&generation, &igbpId, &igbpSlot);
native_handle_t* grallocHandle = UnwrapNativeCodec2GrallocHandle(c2Handle);
sp<GraphicBuffer> graphicBuffer =
new GraphicBuffer(grallocHandle, GraphicBuffer::CLONE_HANDLE, width, height, format,
1, mUsageToBeMigrated, stride);
native_handle_delete(grallocHandle);
if (graphicBuffer->initCheck() != android::NO_ERROR) {
ALOGE("Failed to create GraphicBuffer: %d", graphicBuffer->initCheck());
return false;
}
graphicBuffer->setGenerationNumber(mGenerationToBeMigrated);
// Attach GraphicBuffer to producer.
const auto attachStatus = producer->attachBuffer(graphicBuffer, newSlot);
if (attachStatus == TIMED_OUT || attachStatus == INVALID_OPERATION) {
ALOGV("%s(): No free slot yet.", __func__);
return TIMED_OUT;
}
if (attachStatus != OK) {
ALOGE("%s(): Failed to attach buffer to new producer: %d", __func__, attachStatus);
return attachStatus;
}
ALOGD("%s(), migrated lost buffer uniqueId=%u to slot=%d", __func__, uniqueId, *newSlot);
updateSlotBuffer(*newSlot, uniqueId, graphicBuffer);
// Wrap the new GraphicBuffer to C2GraphicAllocation and register it.
std::shared_ptr<C2GraphicAllocation> allocation =
ConvertGraphicBuffer2C2Allocation(graphicBuffer, producerId, *newSlot, allocator);
if (!allocation) {
return UNKNOWN_ERROR;
}
registerUniqueId(uniqueId, std::move(allocation));
// Note: C2ArcProtectedGraphicAllocator releases the protected buffers if all the
// corrresponding C2GraphicAllocations are released. To prevent the protected buffer is
// released and then allocated again, we release the old C2GraphicAllocation after the new
// one has been created.
mAllocationsToBeMigrated.erase(iter);
return OK;
}
void onBufferDequeued(slot_t slotId) {
ALOGV("%s(slotId=%d)", __func__, slotId);
unique_id_t uniqueId;
std::tie(uniqueId, std::ignore) = getSlotBuffer(slotId);
moveBufferToRegistered(uniqueId);
if (mMigrateLostBufferCounter > 0) {
--mMigrateLostBufferCounter;
}
}
size_t size() const { return mAllocationsRegistered.size() + mAllocationsToBeMigrated.size(); }
std::string debugString() const {
std::stringstream ss;
ss << "tracked size: " << size() << std::endl;
ss << " registered uniqueIds: ";
for (const auto& pair : mAllocationsRegistered) {
ss << pair.first << ", ";
}
ss << std::endl;
ss << " to-be-migrated uniqueIds: ";
for (const auto& pair : mAllocationsToBeMigrated) {
ss << pair.first << ", ";
}
ss << std::endl;
ss << " Count down for lost buffer migration: " << mMigrateLostBufferCounter;
return ss.str();
}
private:
bool moveBufferToRegistered(unique_id_t uniqueId) {
ALOGV("%s(uniqueId=%u)", __func__, uniqueId);
auto iter = mAllocationsToBeMigrated.find(uniqueId);
if (iter == mAllocationsToBeMigrated.end()) {
return false;
}
if (!mAllocationsRegistered.insert(*iter).second) {
ALOGE("%s() duplicated uniqueId=%u", __func__, uniqueId);
return false;
}
mAllocationsToBeMigrated.erase(iter);
return true;
}
// Mapping from IGBP slots to the corresponding graphic buffers.
std::map<slot_t, std::pair<unique_id_t, sp<GraphicBuffer>>> mSlotId2GraphicBuffer;
// Mapping from IGBP slots to the corresponding pool data.
std::map<slot_t, std::weak_ptr<C2BufferQueueBlockPoolData>> mSlotId2PoolData;
// Track the buffers registered at the current producer.
std::map<unique_id_t, std::shared_ptr<C2GraphicAllocation>> mAllocationsRegistered;
// Track the buffers that should be migrated to the current producer.
std::map<unique_id_t, std::shared_ptr<C2GraphicAllocation>> mAllocationsToBeMigrated;
// The counter for migrating lost buffers. Count down when a buffer is
// dequeued from IGBP. When it goes to 0, then we treat the remaining
// buffers at |mAllocationsToBeMigrated| lost, and migrate them to
// current IGBP.
size_t mMigrateLostBufferCounter = 0;
// The generation and usage of the current IGBP, used to migrate buffers.
uint32_t mGenerationToBeMigrated = 0;
uint64_t mUsageToBeMigrated = 0;
};
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, uint32_t width, uint32_t height,
uint32_t format, C2MemoryUsage usage);
bool setNotifyBlockAvailableCb(::base::OnceClosure cb);
std::optional<unique_id_t> getBufferIdFromGraphicBlock(const C2Block2D& block);
private:
// 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);
};
status_t getFreeSlotLocked(uint32_t width, uint32_t height, uint32_t format,
C2MemoryUsage usage, slot_t* slot, sp<Fence>* fence);
// Queries the generation and usage flags from the given producer by dequeuing and requesting a
// buffer (the buffer is then detached and freed).
status_t queryGenerationAndUsageLocked(uint32_t width, uint32_t height, uint32_t pixelFormat,
C2AndroidMemoryUsage androidUsage, uint32_t* generation,
uint64_t* usage);
// Wait the fence. If any error occurs, cancel the buffer back to the producer.
status_t waitFence(slot_t slot, sp<Fence> fence);
// Call mProducer's allowAllocation if needed.
status_t allowAllocation(bool allow);
const std::shared_ptr<C2Allocator> mAllocator;
std::unique_ptr<H2BGraphicBufferProducer> mProducer;
uint64_t mProducerId = 0;
bool mAllowAllocation = false;
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;
TrackedGraphicBuffers mTrackedGraphicBuffers;
// Number of buffers requested on requestNewBufferSet() call.
size_t mBuffersRequested = 0u;
// Currently requested buffer formats.
BufferFormat mBufferFormat;
// 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. Reset when fetching new slot times out, or |mNotifyBlockAvailableCb| is executed.
bool mBufferReleasedAfterTimedOut GUARDED_BY(mBufferReleaseMutex) = false;
// The callback to notify the caller the buffer is available.
::base::OnceClosure mNotifyBlockAvailableCb GUARDED_BY(mBufferReleaseMutex);
// Set to true if any error occurs at previous configureProducer().
bool mConfigureProducerError = false;
};
C2VdaBqBlockPool::Impl::Impl(const std::shared_ptr<C2Allocator>& allocator)
: mAllocator(allocator) {}
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(%ux%u)", __func__, width, height);
std::lock_guard<std::mutex> lock(mMutex);
if (width != mBufferFormat.mWidth || height != mBufferFormat.mHeight ||
format != mBufferFormat.mPixelFormat || usage.expected != mBufferFormat.mUsage.expected) {
ALOGE("%s(): buffer format (%ux%u, format=%u, usage=%" PRIx64
") is different from requested format (%ux%u, format=%u, usage=%" PRIx64 ")",
__func__, width, height, format, usage.expected, mBufferFormat.mWidth,
mBufferFormat.mHeight, mBufferFormat.mPixelFormat, mBufferFormat.mUsage.expected);
return C2_BAD_VALUE;
}
if (mConfigureProducerError || !mProducer) {
ALOGE("%s(): error occurred at previous configureProducer()", __func__);
return C2_CORRUPTED;
}
slot_t slot;
sp<Fence> fence = new Fence();
const auto status = getFreeSlotLocked(width, height, format, usage, &slot, &fence);
if (status != OK) {
return asC2Error(status);
}
unique_id_t uniqueId;
sp<GraphicBuffer> slotBuffer;
std::tie(uniqueId, slotBuffer) = mTrackedGraphicBuffers.getSlotBuffer(slot);
ALOGV("%s(): dequeued slot=%d uniqueId=%u", __func__, slot, uniqueId);
if (!mTrackedGraphicBuffers.hasUniqueId(uniqueId)) {
if (mTrackedGraphicBuffers.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 %d but already allocated enough buffers. Detach it.", slot);
if (mProducer->detachBuffer(slot) != OK) {
return C2_CORRUPTED;
}
const auto allocationStatus = allowAllocation(false);
if (allocationStatus != OK) {
return asC2Error(allocationStatus);
}
return C2_TIMED_OUT;
}
std::shared_ptr<C2GraphicAllocation> allocation =
ConvertGraphicBuffer2C2Allocation(slotBuffer, mProducerId, slot, mAllocator.get());
if (!allocation) {
return C2_CORRUPTED;
}
mTrackedGraphicBuffers.registerUniqueId(uniqueId, std::move(allocation));
ALOGV("%s(): mTrackedGraphicBuffers.size=%zu", __func__, mTrackedGraphicBuffers.size());
if (mTrackedGraphicBuffers.size() == mBuffersRequested) {
ALOGV("Tracked IGBP slots: %s", mTrackedGraphicBuffers.debugString().c_str());
// Already allocated enough buffers, set allowAllocation to false to restrict the
// eligible slots to allocated ones for future dequeue.
const auto allocationStatus = allowAllocation(false);
if (allocationStatus != OK) {
return asC2Error(allocationStatus);
}
}
}
std::shared_ptr<C2SurfaceSyncMemory> syncMem;
// TODO: the |owner| argument should be set correctly.
std::shared_ptr<C2GraphicAllocation> allocation =
mTrackedGraphicBuffers.getRegisteredAllocation(uniqueId);
auto poolData = std::make_shared<C2BufferQueueBlockPoolData>(
slotBuffer->getGenerationNumber(), mProducerId, slot, std::make_shared<int>(0),
mProducer->getBase(), syncMem);
mTrackedGraphicBuffers.updatePoolData(slot, poolData);
*block = _C2BlockFactory::CreateGraphicBlock(std::move(allocation), std::move(poolData));
if (*block == nullptr) {
ALOGE("failed to create GraphicBlock: no memory");
return C2_NO_MEMORY;
}
// Wait for acquire fence at the last point of returning buffer.
if (fence) {
const auto fenceStatus = waitFence(slot, fence);
if (fenceStatus != OK) {
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);
}
}
}
return C2_OK;
}
status_t C2VdaBqBlockPool::Impl::getFreeSlotLocked(uint32_t width, uint32_t height, uint32_t format,
C2MemoryUsage usage, slot_t* slot,
sp<Fence>* fence) {
if (mTrackedGraphicBuffers.needMigrateLostBuffers()) {
slot_t newSlot;
if (mTrackedGraphicBuffers.migrateLostBuffer(mAllocator.get(), mProducer.get(), mProducerId,
&newSlot) == OK) {
ALOGV("%s(): migrated buffer: slot=%d", __func__, newSlot);
*slot = newSlot;
return OK;
}
}
// Dequeue a free slot from IGBP.
ALOGV("%s(): try to dequeue free slot from IGBP.", __func__);
const auto dequeueStatus = mProducer->dequeueBuffer(width, height, format, usage, slot, fence);
if (dequeueStatus == TIMED_OUT) {
std::lock_guard<std::mutex> lock(mBufferReleaseMutex);
mBufferReleasedAfterTimedOut = false;
}
if (dequeueStatus != OK && dequeueStatus != BUFFER_NEEDS_REALLOCATION) {
return dequeueStatus;
}
// Call requestBuffer to update GraphicBuffer for the slot and obtain the reference.
if (!mTrackedGraphicBuffers.hasSlotId(*slot) || dequeueStatus == BUFFER_NEEDS_REALLOCATION) {
sp<GraphicBuffer> slotBuffer = new GraphicBuffer();
const auto requestStatus = mProducer->requestBuffer(*slot, &slotBuffer);
if (requestStatus != OK) {
mProducer->cancelBuffer(*slot, *fence);
return requestStatus;
}
const auto uniqueId = getDmabufId(slotBuffer->handle->data[0]);
if (!uniqueId) {
ALOGE("%s(): failed to get uniqueId of GraphicBuffer from slot=%d", __func__, *slot);
return UNKNOWN_ERROR;
}
mTrackedGraphicBuffers.updateSlotBuffer(*slot, *uniqueId, std::move(slotBuffer));
}
ALOGV("%s(%ux%u): dequeued slot=%d", __func__, mBufferFormat.mWidth, mBufferFormat.mHeight,
*slot);
mTrackedGraphicBuffers.onBufferDequeued(*slot);
return OK;
}
void C2VdaBqBlockPool::Impl::onEventNotified() {
ALOGV("%s()", __func__);
::base::OnceClosure outputCb;
{
std::lock_guard<std::mutex> lock(mBufferReleaseMutex);
mBufferReleasedAfterTimedOut = true;
if (mNotifyBlockAvailableCb) {
mBufferReleasedAfterTimedOut = false;
outputCb = std::move(mNotifyBlockAvailableCb);
}
}
// Calling the callback outside the lock to avoid the deadlock.
if (outputCb) {
std::move(outputCb).Run();
}
}
status_t C2VdaBqBlockPool::Impl::queryGenerationAndUsageLocked(uint32_t width, uint32_t height,
uint32_t pixelFormat,
C2AndroidMemoryUsage androidUsage,
uint32_t* generation,
uint64_t* usage) {
ALOGV("%s()", __func__);
sp<Fence> fence = new Fence();
slot_t slot;
const auto dequeueStatus =
mProducer->dequeueBuffer(width, height, pixelFormat, androidUsage, &slot, &fence);
if (dequeueStatus != OK && dequeueStatus != BUFFER_NEEDS_REALLOCATION) {
return dequeueStatus;
}
// Call requestBuffer to allocate buffer for the slot and obtain the reference.
// Get generation number here.
sp<GraphicBuffer> slotBuffer = new GraphicBuffer();
const auto requestStatus = mProducer->requestBuffer(slot, &slotBuffer);
// Detach and delete the temporary buffer.
const auto detachStatus = mProducer->detachBuffer(slot);
if (detachStatus != OK) {
return detachStatus;
}
// Check requestBuffer return flag.
if (requestStatus != OK) {
return requestStatus;
}
// 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 OK;
}
status_t C2VdaBqBlockPool::Impl::waitFence(slot_t slot, sp<Fence> fence) {
const auto fenceStatus = fence->wait(kFenceWaitTimeMs);
if (fenceStatus == OK) {
return OK;
}
const auto cancelStatus = mProducer->cancelBuffer(slot, fence);
if (cancelStatus != OK) {
ALOGE("%s(): failed to cancelBuffer(slot=%d)", __func__, slot);
return cancelStatus;
}
if (fenceStatus == -ETIME) { // fence wait timed out
ALOGV("%s(): buffer (slot=%d) fence wait timed out", __func__, slot);
return TIMED_OUT;
}
ALOGE("buffer fence wait error: %d", fenceStatus);
return fenceStatus;
}
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, uint32_t width,
uint32_t height, uint32_t format,
C2MemoryUsage usage) {
ALOGV("%s(bufferCount=%d, size=%ux%u, format=0x%x, usage=%" PRIu64 ")", __func__, bufferCount,
width, height, format, usage.expected);
if (bufferCount <= 0) {
ALOGE("Invalid requested buffer count = %d", bufferCount);
return C2_BAD_VALUE;
}
std::lock_guard<std::mutex> lock(mMutex);
if (!mProducer) {
ALOGD("No HGraphicBufferProducer is configured...");
return C2_NO_INIT;
}
if (mBuffersRequested == static_cast<size_t>(bufferCount) && mBufferFormat.mWidth == width &&
mBufferFormat.mHeight == height && mBufferFormat.mPixelFormat == format &&
mBufferFormat.mUsage.expected == usage.expected) {
ALOGD("%s() Request the same format and amount of buffers, skip", __func__);
return C2_OK;
}
const auto status = allowAllocation(true);
if (status != OK) {
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.
mTrackedGraphicBuffers.reset();
mBuffersRequested = static_cast<size_t>(bufferCount);
// Store buffer formats for future usage.
mBufferFormat = BufferFormat(width, height, format, C2AndroidMemoryUsage(usage));
return C2_OK;
}
void C2VdaBqBlockPool::Impl::configureProducer(const sp<HGraphicBufferProducer>& producer) {
ALOGV("%s(producer=%p)", __func__, producer.get());
std::lock_guard<std::mutex> lock(mMutex);
if (producer == nullptr) {
ALOGI("input producer is nullptr...");
mProducer = nullptr;
mProducerId = 0;
mTrackedGraphicBuffers.reset();
return;
}
auto newProducer = std::make_unique<H2BGraphicBufferProducer>(producer);
uint64_t newProducerId;
if (newProducer->getUniqueId(&newProducerId) != OK) {
ALOGE("%s(): failed to get IGBP ID", __func__);
mConfigureProducerError = true;
return;
}
if (newProducerId == mProducerId) {
ALOGI("%s(): configure the same producer, ignore", __func__);
return;
}
ALOGI("Producer (Surface) is going to switch... ( 0x%" PRIx64 " -> 0x%" PRIx64 " )",
mProducerId, newProducerId);
mProducer = std::move(newProducer);
mProducerId = newProducerId;
mConfigureProducerError = false;
mAllowAllocation = false;
// Set allowAllocation to new producer.
if (allowAllocation(true) != OK) {
ALOGE("%s(): failed to allowAllocation(true)", __func__);
mConfigureProducerError = true;
return;
}
if (mProducer->setDequeueTimeout(0) != OK) {
ALOGE("%s(): failed to setDequeueTimeout(0)", __func__);
mConfigureProducerError = true;
return;
}
if (mProducer->setMaxDequeuedBufferCount(kMaxDequeuedBufferCount) != OK) {
ALOGE("%s(): failed to setMaxDequeuedBufferCount(%d)", __func__, kMaxDequeuedBufferCount);
mConfigureProducerError = true;
return;
}
// Migrate existing buffers to the new producer.
if (mTrackedGraphicBuffers.size() > 0) {
uint32_t newGeneration = 0;
uint64_t newUsage = 0;
const status_t err = queryGenerationAndUsageLocked(
mBufferFormat.mWidth, mBufferFormat.mHeight, mBufferFormat.mPixelFormat,
mBufferFormat.mUsage, &newGeneration, &newUsage);
if (err != OK) {
ALOGE("failed to query generation and usage: %d", err);
mConfigureProducerError = true;
return;
}
if (!mTrackedGraphicBuffers.migrateLocalBuffers(mProducer.get(), mProducerId, newGeneration,
newUsage)) {
ALOGE("%s(): failed to migrateLocalBuffers()", __func__);
mConfigureProducerError = true;
return;
}
if (mTrackedGraphicBuffers.size() == mBuffersRequested) {
if (allowAllocation(false) != OK) {
ALOGE("%s(): failed to allowAllocation(false)", __func__);
mConfigureProducerError = true;
return;
}
}
}
// hack(b/146409777): Try to connect ARC-specific listener first.
sp<BufferReleasedNotifier> listener = new BufferReleasedNotifier(weak_from_this());
if (mProducer->connect(listener, 'ARC\0', false) == OK) {
ALOGI("connected to ARC-specific IGBP listener.");
mFetchBufferNotifier = listener;
}
// There might be free buffers at the new producer, notify the client if needed.
onEventNotified();
}
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) {
mBufferReleasedAfterTimedOut = false;
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;
}
std::optional<unique_id_t> C2VdaBqBlockPool::Impl::getBufferIdFromGraphicBlock(
const C2Block2D& block) {
return getDmabufId(block.handle()->data[0]);
}
status_t C2VdaBqBlockPool::Impl::allowAllocation(bool allow) {
ALOGV("%s(%d)", __func__, allow);
if (!mProducer) {
ALOGW("%s() mProducer is not initiailzed", __func__);
return NO_INIT;
}
if (mAllowAllocation == allow) {
return OK;
}
const auto status = mProducer->allowAllocation(allow);
if (status == OK) {
mAllowAllocation = allow;
}
return status;
}
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, uint32_t width,
uint32_t height, uint32_t format,
C2MemoryUsage usage) {
if (mImpl) {
return mImpl->requestNewBufferSet(bufferCount, width, height, format, usage);
}
return C2_NO_INIT;
}
void C2VdaBqBlockPool::configureProducer(const sp<HGraphicBufferProducer>& producer) {
if (mImpl) {
mImpl->configureProducer(producer);
}
}
bool C2VdaBqBlockPool::setNotifyBlockAvailableCb(::base::OnceClosure cb) {
if (mImpl) {
return mImpl->setNotifyBlockAvailableCb(std::move(cb));
}
return false;
}
std::optional<unique_id_t> C2VdaBqBlockPool::getBufferIdFromGraphicBlock(const C2Block2D& block) {
if (mImpl) {
return mImpl->getBufferIdFromGraphicBlock(block);
}
return std::nullopt;
}
} // namespace android