|  | /* | 
|  | * Copyright 2018 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | //#define LOG_NDEBUG 0 | 
|  | #define LOG_TAG "Codec2-OutputBufferQueue" | 
|  | #include <android-base/logging.h> | 
|  |  | 
|  | #include <android/hardware/graphics/bufferqueue/2.0/IGraphicBufferProducer.h> | 
|  | #include <codec2/hidl/output.h> | 
|  | #include <cutils/ashmem.h> | 
|  | #include <gui/bufferqueue/2.0/B2HGraphicBufferProducer.h> | 
|  | #include <sys/mman.h> | 
|  |  | 
|  | #include <C2AllocatorGralloc.h> | 
|  | #include <C2BlockInternal.h> | 
|  | #include <C2Buffer.h> | 
|  | #include <C2PlatformSupport.h> | 
|  | #include <C2SurfaceSyncObj.h> | 
|  |  | 
|  | #include <iomanip> | 
|  |  | 
|  | namespace android { | 
|  | namespace hardware { | 
|  | namespace media { | 
|  | namespace c2 { | 
|  |  | 
|  | using HGraphicBufferProducer = ::android::hardware::graphics::bufferqueue:: | 
|  | V2_0::IGraphicBufferProducer; | 
|  | using B2HGraphicBufferProducer = ::android::hardware::graphics::bufferqueue:: | 
|  | V2_0::utils::B2HGraphicBufferProducer; | 
|  |  | 
|  | namespace /* unnamed */ { | 
|  |  | 
|  | // Create a GraphicBuffer object from a graphic block. | 
|  | sp<GraphicBuffer> createGraphicBuffer(const C2ConstGraphicBlock& block) { | 
|  | uint32_t width; | 
|  | uint32_t height; | 
|  | uint32_t format; | 
|  | uint64_t usage; | 
|  | uint32_t stride; | 
|  | uint32_t generation; | 
|  | uint64_t bqId; | 
|  | int32_t bqSlot; | 
|  | _UnwrapNativeCodec2GrallocMetadata( | 
|  | block.handle(), &width, &height, &format, &usage, | 
|  | &stride, &generation, &bqId, reinterpret_cast<uint32_t*>(&bqSlot)); | 
|  | native_handle_t *grallocHandle = | 
|  | UnwrapNativeCodec2GrallocHandle(block.handle()); | 
|  | sp<GraphicBuffer> graphicBuffer = | 
|  | new GraphicBuffer(grallocHandle, | 
|  | GraphicBuffer::CLONE_HANDLE, | 
|  | width, height, format, | 
|  | 1, usage, stride); | 
|  | native_handle_delete(grallocHandle); | 
|  | return graphicBuffer; | 
|  | } | 
|  |  | 
|  | template <typename BlockProcessor> | 
|  | void forEachBlock(C2FrameData& frameData, | 
|  | BlockProcessor process) { | 
|  | for (const std::shared_ptr<C2Buffer>& buffer : frameData.buffers) { | 
|  | if (buffer) { | 
|  | for (const C2ConstGraphicBlock& block : | 
|  | buffer->data().graphicBlocks()) { | 
|  | process(block); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | template <typename BlockProcessor> | 
|  | void forEachBlock(const std::list<std::unique_ptr<C2Work>>& workList, | 
|  | BlockProcessor process) { | 
|  | for (const std::unique_ptr<C2Work>& work : workList) { | 
|  | if (!work) { | 
|  | continue; | 
|  | } | 
|  | for (const std::unique_ptr<C2Worklet>& worklet : work->worklets) { | 
|  | if (worklet) { | 
|  | forEachBlock(worklet->output, process); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | sp<HGraphicBufferProducer> getHgbp(const sp<IGraphicBufferProducer>& igbp) { | 
|  | sp<HGraphicBufferProducer> hgbp = | 
|  | igbp->getHalInterface<HGraphicBufferProducer>(); | 
|  | return hgbp ? hgbp : | 
|  | new B2HGraphicBufferProducer(igbp); | 
|  | } | 
|  |  | 
|  | status_t attachToBufferQueue(const C2ConstGraphicBlock& block, | 
|  | const sp<IGraphicBufferProducer>& igbp, | 
|  | uint32_t generation, | 
|  | int32_t* bqSlot, | 
|  | std::shared_ptr<C2SurfaceSyncMemory> syncMem) { | 
|  | if (!igbp) { | 
|  | LOG(WARNING) << "attachToBufferQueue -- null producer."; | 
|  | return NO_INIT; | 
|  | } | 
|  |  | 
|  | sp<GraphicBuffer> graphicBuffer = createGraphicBuffer(block); | 
|  | graphicBuffer->setGenerationNumber(generation); | 
|  |  | 
|  | LOG(VERBOSE) << "attachToBufferQueue -- attaching buffer:" | 
|  | << " block dimension " << block.width() << "x" | 
|  | << block.height() | 
|  | << ", graphicBuffer dimension " << graphicBuffer->getWidth() << "x" | 
|  | << graphicBuffer->getHeight() | 
|  | << std::hex << std::setfill('0') | 
|  | << ", format 0x" << std::setw(8) << graphicBuffer->getPixelFormat() | 
|  | << ", usage 0x" << std::setw(16) << graphicBuffer->getUsage() | 
|  | << std::dec << std::setfill(' ') | 
|  | << ", stride " << graphicBuffer->getStride() | 
|  | << ", generation " << graphicBuffer->getGenerationNumber(); | 
|  |  | 
|  | C2SyncVariables *syncVar = syncMem ? syncMem->mem() : nullptr; | 
|  | status_t result = OK; | 
|  | if (syncVar) { | 
|  | syncVar->lock(); | 
|  | if (!syncVar->isDequeueableLocked() || | 
|  | syncVar->getSyncStatusLocked() == C2SyncVariables::STATUS_SWITCHING) { | 
|  | syncVar->unlock(); | 
|  | LOG(WARNING) << "attachToBufferQueue -- attachBuffer failed: " | 
|  | "status = " << INVALID_OPERATION << "."; | 
|  | return INVALID_OPERATION; | 
|  | } | 
|  | result = igbp->attachBuffer(bqSlot, graphicBuffer); | 
|  | if (result == OK) { | 
|  | syncVar->notifyDequeuedLocked(); | 
|  | } | 
|  | syncVar->unlock(); | 
|  | } else { | 
|  | result = igbp->attachBuffer(bqSlot, graphicBuffer); | 
|  | } | 
|  | if (result != OK) { | 
|  | LOG(WARNING) << "attachToBufferQueue -- attachBuffer failed: " | 
|  | "status = " << result << "."; | 
|  | return result; | 
|  | } | 
|  | LOG(VERBOSE) << "attachToBufferQueue -- attachBuffer returned slot #" | 
|  | << *bqSlot << "."; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | bool getBufferQueueAssignment(const C2ConstGraphicBlock& block, | 
|  | uint32_t* generation, | 
|  | uint64_t* bqId, | 
|  | int32_t* bqSlot) { | 
|  | return _C2BlockFactory::GetBufferQueueData( | 
|  | _C2BlockFactory::GetGraphicBlockPoolData(block), | 
|  | generation, bqId, bqSlot); | 
|  | } | 
|  |  | 
|  | } // unnamed namespace | 
|  |  | 
|  | OutputBufferQueue::OutputBufferQueue() | 
|  | : mGeneration{0}, mBqId{0}, mStopped{false} { | 
|  | } | 
|  |  | 
|  | OutputBufferQueue::~OutputBufferQueue() { | 
|  | } | 
|  |  | 
|  | bool OutputBufferQueue::configure(const sp<IGraphicBufferProducer>& igbp, | 
|  | uint32_t generation, | 
|  | uint64_t bqId, | 
|  | int maxDequeueBufferCount, | 
|  | std::shared_ptr<V1_2::SurfaceSyncObj> *syncObj) { | 
|  | uint64_t consumerUsage = 0; | 
|  | if (igbp && igbp->getConsumerUsage(&consumerUsage) != OK) { | 
|  | ALOGW("failed to get consumer usage"); | 
|  | } | 
|  |  | 
|  | // TODO : Abstract creation process into C2SurfaceSyncMemory class. | 
|  | // use C2LinearBlock instead ashmem. | 
|  | std::shared_ptr<C2SurfaceSyncMemory> syncMem; | 
|  | if (syncObj && igbp) { | 
|  | bool mapped = false; | 
|  | int memFd = ashmem_create_region("C2SurfaceMem", sizeof(C2SyncVariables)); | 
|  | size_t memSize = memFd < 0 ? 0 : ashmem_get_size_region(memFd); | 
|  | if (memSize > 0) { | 
|  | syncMem = C2SurfaceSyncMemory::Create(memFd, memSize); | 
|  | if (syncMem) { | 
|  | mapped = true; | 
|  | *syncObj = std::make_shared<V1_2::SurfaceSyncObj>(); | 
|  | (*syncObj)->syncMemory = syncMem->handle(); | 
|  | (*syncObj)->bqId = bqId; | 
|  | (*syncObj)->generationId = generation; | 
|  | (*syncObj)->consumerUsage = consumerUsage; | 
|  | ALOGD("C2SurfaceSyncMemory created %zu(%zu)", sizeof(C2SyncVariables), memSize); | 
|  | } | 
|  | } | 
|  | if (!mapped) { | 
|  | if (memFd >= 0) { | 
|  | ::close(memFd); | 
|  | } | 
|  | ALOGW("SurfaceSyncObj creation failure"); | 
|  | } | 
|  | } | 
|  |  | 
|  | size_t tryNum = 0; | 
|  | size_t success = 0; | 
|  | sp<GraphicBuffer> buffers[BufferQueueDefs::NUM_BUFFER_SLOTS]; | 
|  | std::weak_ptr<_C2BlockPoolData> | 
|  | poolDatas[BufferQueueDefs::NUM_BUFFER_SLOTS]; | 
|  | std::shared_ptr<C2SurfaceSyncMemory> oldMem; | 
|  | { | 
|  | std::scoped_lock<std::mutex> l(mMutex); | 
|  | bool stopped = mStopped; | 
|  | mStopped = false; | 
|  | if (generation == mGeneration) { | 
|  | // case of old BlockPool destruction | 
|  | C2SyncVariables *var = mSyncMem ? mSyncMem->mem() : nullptr; | 
|  | if (syncObj && var) { | 
|  | *syncObj = std::make_shared<V1_2::SurfaceSyncObj>(); | 
|  | (*syncObj)->bqId = bqId; | 
|  | (*syncObj)->syncMemory = mSyncMem->handle(); | 
|  | (*syncObj)->generationId = generation; | 
|  | (*syncObj)->consumerUsage = consumerUsage; | 
|  | mMaxDequeueBufferCount = maxDequeueBufferCount; | 
|  | var->lock(); | 
|  | var->setSyncStatusLocked(C2SyncVariables::STATUS_INIT); | 
|  | var->setInitialDequeueCountLocked(mMaxDequeueBufferCount, 0); | 
|  | var->unlock(); | 
|  | } | 
|  | return false; | 
|  | } | 
|  | oldMem = mSyncMem; | 
|  | C2SyncVariables *oldSync = mSyncMem ? mSyncMem->mem() : nullptr; | 
|  | if (oldSync) { | 
|  | oldSync->lock(); | 
|  | oldSync->setSyncStatusLocked(C2SyncVariables::STATUS_SWITCHING); | 
|  | oldSync->unlock(); | 
|  | } | 
|  | mSyncMem.reset(); | 
|  | if (syncMem) { | 
|  | mSyncMem = syncMem; | 
|  | } | 
|  | C2SyncVariables *newSync = mSyncMem ? mSyncMem->mem() : nullptr; | 
|  |  | 
|  | mIgbp = igbp; | 
|  | mGeneration = generation; | 
|  | mBqId = bqId; | 
|  | mOwner = std::make_shared<int>(0); | 
|  | mMaxDequeueBufferCount = maxDequeueBufferCount; | 
|  | if (igbp == nullptr) { | 
|  | return false; | 
|  | } | 
|  | for (int i = 0; i < BufferQueueDefs::NUM_BUFFER_SLOTS; ++i) { | 
|  | if (mBqId == 0 || !mBuffers[i] || stopped) { | 
|  | continue; | 
|  | } | 
|  | std::shared_ptr<_C2BlockPoolData> data = mPoolDatas[i].lock(); | 
|  | if (!data || | 
|  | !_C2BlockFactory::BeginAttachBlockToBufferQueue(data)) { | 
|  | continue; | 
|  | } | 
|  | ++tryNum; | 
|  | int bqSlot; | 
|  |  | 
|  | // Update buffer's generation and usage. | 
|  | if ((mBuffers[i]->getUsage() & consumerUsage) != consumerUsage) { | 
|  | mBuffers[i] = new GraphicBuffer( | 
|  | mBuffers[i]->handle, GraphicBuffer::CLONE_HANDLE, | 
|  | mBuffers[i]->width, mBuffers[i]->height, | 
|  | mBuffers[i]->format, mBuffers[i]->layerCount, | 
|  | mBuffers[i]->getUsage() | consumerUsage, | 
|  | mBuffers[i]->stride); | 
|  | if (mBuffers[i]->initCheck() != OK) { | 
|  | ALOGW("%s() failed to update usage, original usage=%" PRIx64 | 
|  | ", consumer usage=%" PRIx64, | 
|  | __func__, mBuffers[i]->getUsage(), consumerUsage); | 
|  | continue; | 
|  | } | 
|  | } | 
|  | mBuffers[i]->setGenerationNumber(generation); | 
|  |  | 
|  | status_t result = igbp->attachBuffer(&bqSlot, mBuffers[i]); | 
|  | if (result != OK) { | 
|  | continue; | 
|  | } | 
|  | bool attach = | 
|  | _C2BlockFactory::EndAttachBlockToBufferQueue( | 
|  | data, mOwner, getHgbp(mIgbp), mSyncMem, | 
|  | generation, bqId, bqSlot); | 
|  | if (!attach) { | 
|  | igbp->cancelBuffer(bqSlot, Fence::NO_FENCE); | 
|  | continue; | 
|  | } | 
|  | buffers[bqSlot] = mBuffers[i]; | 
|  | poolDatas[bqSlot] = data; | 
|  | ++success; | 
|  | } | 
|  | for (int i = 0; i < BufferQueueDefs::NUM_BUFFER_SLOTS; ++i) { | 
|  | mBuffers[i] = buffers[i]; | 
|  | mPoolDatas[i] = poolDatas[i]; | 
|  | } | 
|  | if (newSync) { | 
|  | newSync->lock(); | 
|  | newSync->setInitialDequeueCountLocked(mMaxDequeueBufferCount, success); | 
|  | newSync->unlock(); | 
|  | } | 
|  | } | 
|  | { | 
|  | std::scoped_lock<std::mutex> l(mOldMutex); | 
|  | mOldMem = oldMem; | 
|  | } | 
|  | ALOGD("remote graphic buffer migration %zu/%zu", | 
|  | success, tryNum); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void OutputBufferQueue::expireOldWaiters() { | 
|  | std::scoped_lock<std::mutex> l(mOldMutex); | 
|  | if (mOldMem) { | 
|  | C2SyncVariables *oldSync = mOldMem->mem(); | 
|  | if (oldSync) { | 
|  | oldSync->notifyAll(); | 
|  | } | 
|  | mOldMem.reset(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void OutputBufferQueue::stop() { | 
|  | std::scoped_lock<std::mutex> l(mMutex); | 
|  | mStopped = true; | 
|  | mOwner.reset(); // destructor of the block will not triger IGBP::cancel() | 
|  | } | 
|  |  | 
|  | bool OutputBufferQueue::registerBuffer(const C2ConstGraphicBlock& block) { | 
|  | std::shared_ptr<_C2BlockPoolData> data = | 
|  | _C2BlockFactory::GetGraphicBlockPoolData(block); | 
|  | if (!data) { | 
|  | return false; | 
|  | } | 
|  | std::scoped_lock<std::mutex> l(mMutex); | 
|  |  | 
|  | if (!mIgbp || mStopped) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | uint32_t oldGeneration; | 
|  | uint64_t oldId; | 
|  | int32_t oldSlot; | 
|  | // If the block is not bufferqueue-based, do nothing. | 
|  | if (!_C2BlockFactory::GetBufferQueueData( | 
|  | data, &oldGeneration, &oldId, &oldSlot) || (oldId == 0)) { | 
|  | return false; | 
|  | } | 
|  | // If the block's bqId is the same as the desired bqId, just hold. | 
|  | if ((oldId == mBqId) && (oldGeneration == mGeneration)) { | 
|  | LOG(VERBOSE) << "holdBufferQueueBlock -- import without attaching:" | 
|  | << " bqId " << oldId | 
|  | << ", bqSlot " << oldSlot | 
|  | << ", generation " << mGeneration | 
|  | << "."; | 
|  | _C2BlockFactory::HoldBlockFromBufferQueue(data, mOwner, getHgbp(mIgbp), mSyncMem); | 
|  | mPoolDatas[oldSlot] = data; | 
|  | mBuffers[oldSlot] = createGraphicBuffer(block); | 
|  | mBuffers[oldSlot]->setGenerationNumber(mGeneration); | 
|  | return true; | 
|  | } | 
|  | int32_t d = (int32_t) mGeneration - (int32_t) oldGeneration; | 
|  | LOG(WARNING) << "receiving stale buffer: generation " | 
|  | << mGeneration << " , diff " << d  << " : slot " | 
|  | << oldSlot; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | status_t OutputBufferQueue::outputBuffer( | 
|  | const C2ConstGraphicBlock& block, | 
|  | const BnGraphicBufferProducer::QueueBufferInput& input, | 
|  | BnGraphicBufferProducer::QueueBufferOutput* output) { | 
|  | uint32_t generation; | 
|  | uint64_t bqId; | 
|  | int32_t bqSlot; | 
|  | bool display = V1_0::utils::displayBufferQueueBlock(block); | 
|  | if (!getBufferQueueAssignment(block, &generation, &bqId, &bqSlot) || | 
|  | bqId == 0) { | 
|  | // Block not from bufferqueue -- it must be attached before queuing. | 
|  |  | 
|  | std::shared_ptr<C2SurfaceSyncMemory> syncMem; | 
|  | mMutex.lock(); | 
|  | bool stopped = mStopped; | 
|  | sp<IGraphicBufferProducer> outputIgbp = mIgbp; | 
|  | uint32_t outputGeneration = mGeneration; | 
|  | syncMem = mSyncMem; | 
|  | mMutex.unlock(); | 
|  |  | 
|  | if (stopped) { | 
|  | LOG(INFO) << "outputBuffer -- already stopped."; | 
|  | return DEAD_OBJECT; | 
|  | } | 
|  |  | 
|  | status_t status = attachToBufferQueue( | 
|  | block, outputIgbp, outputGeneration, &bqSlot, syncMem); | 
|  |  | 
|  | if (status != OK) { | 
|  | LOG(WARNING) << "outputBuffer -- attaching failed."; | 
|  | return INVALID_OPERATION; | 
|  | } | 
|  |  | 
|  | auto syncVar = syncMem ? syncMem->mem() : nullptr; | 
|  | if(syncVar) { | 
|  | syncVar->lock(); | 
|  | status = outputIgbp->queueBuffer(static_cast<int>(bqSlot), | 
|  | input, output); | 
|  | if (status == OK) { | 
|  | syncVar->notifyQueuedLocked(); | 
|  | } | 
|  | syncVar->unlock(); | 
|  | } else { | 
|  | status = outputIgbp->queueBuffer(static_cast<int>(bqSlot), | 
|  | input, output); | 
|  | } | 
|  | if (status != OK) { | 
|  | LOG(ERROR) << "outputBuffer -- queueBuffer() failed " | 
|  | "on non-bufferqueue-based block. " | 
|  | "Error = " << status << "."; | 
|  | return status; | 
|  | } | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | std::shared_ptr<C2SurfaceSyncMemory> syncMem; | 
|  | mMutex.lock(); | 
|  | bool stopped = mStopped; | 
|  | sp<IGraphicBufferProducer> outputIgbp = mIgbp; | 
|  | uint32_t outputGeneration = mGeneration; | 
|  | uint64_t outputBqId = mBqId; | 
|  | syncMem = mSyncMem; | 
|  | mMutex.unlock(); | 
|  |  | 
|  | if (stopped) { | 
|  | LOG(INFO) << "outputBuffer -- already stopped."; | 
|  | return DEAD_OBJECT; | 
|  | } | 
|  |  | 
|  | if (!outputIgbp) { | 
|  | LOG(VERBOSE) << "outputBuffer -- output surface is null."; | 
|  | return NO_INIT; | 
|  | } | 
|  |  | 
|  | if (!display) { | 
|  | LOG(WARNING) << "outputBuffer -- cannot display " | 
|  | "bufferqueue-based block to the bufferqueue."; | 
|  | return UNKNOWN_ERROR; | 
|  | } | 
|  | if (bqId != outputBqId || generation != outputGeneration) { | 
|  | int32_t diff = (int32_t) outputGeneration - (int32_t) generation; | 
|  | LOG(WARNING) << "outputBuffer -- buffers from old generation to " | 
|  | << outputGeneration << " , diff: " << diff | 
|  | << " , slot: " << bqSlot; | 
|  | return DEAD_OBJECT; | 
|  | } | 
|  |  | 
|  | auto syncVar = syncMem ? syncMem->mem() : nullptr; | 
|  | status_t status = OK; | 
|  | if (syncVar) { | 
|  | syncVar->lock(); | 
|  | status = outputIgbp->queueBuffer(static_cast<int>(bqSlot), | 
|  | input, output); | 
|  | if (status == OK) { | 
|  | syncVar->notifyQueuedLocked(); | 
|  | } | 
|  | syncVar->unlock(); | 
|  | } else { | 
|  | status = outputIgbp->queueBuffer(static_cast<int>(bqSlot), | 
|  | input, output); | 
|  | } | 
|  |  | 
|  | if (status != OK) { | 
|  | LOG(ERROR) << "outputBuffer -- queueBuffer() failed " | 
|  | "on bufferqueue-based block. " | 
|  | "Error = " << status << "."; | 
|  | return status; | 
|  | } | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | void OutputBufferQueue::holdBufferQueueBlocks( | 
|  | const std::list<std::unique_ptr<C2Work>>& workList) { | 
|  | forEachBlock(workList, | 
|  | std::bind(&OutputBufferQueue::registerBuffer, | 
|  | this, std::placeholders::_1)); | 
|  | } | 
|  |  | 
|  | void OutputBufferQueue::updateMaxDequeueBufferCount(int maxDequeueBufferCount) { | 
|  | mMutex.lock(); | 
|  | mMaxDequeueBufferCount = maxDequeueBufferCount; | 
|  | auto syncVar = mSyncMem ? mSyncMem->mem() : nullptr; | 
|  | if (syncVar && !mStopped) { | 
|  | syncVar->lock(); | 
|  | syncVar->updateMaxDequeueCountLocked(maxDequeueBufferCount); | 
|  | syncVar->unlock(); | 
|  | } | 
|  | mMutex.unlock(); | 
|  | ALOGD("set max dequeue count %d from update", maxDequeueBufferCount); | 
|  | } | 
|  |  | 
|  | }  // namespace c2 | 
|  | }  // namespace media | 
|  | }  // namespace hardware | 
|  | }  // namespace android | 
|  |  |