/* * 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. */ #ifdef __LP64__ #define OMX_ANDROID_COMPILE_AS_32BIT_ON_64BIT_PLATFORMS #endif //#define LOG_NDEBUG 0 #define LOG_TAG "C2OMXNode" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils/Codec2Mapper.h" #include "C2OMXNode.h" namespace android { namespace { constexpr OMX_U32 kPortIndexInput = 0; class Buffer2D : public C2Buffer { public: explicit Buffer2D(C2ConstGraphicBlock block) : C2Buffer({ block }) {} }; } // namespace class C2OMXNode::QueueThread : public Thread { public: QueueThread() : Thread(false) {} ~QueueThread() override = default; void queue( const std::shared_ptr &comp, int fenceFd, std::unique_ptr &&work, android::base::unique_fd &&fd0, android::base::unique_fd &&fd1) { Mutexed::Locked jobs(mJobs); auto it = jobs->queues.try_emplace(comp, comp).first; it->second.workList.emplace_back( std::move(work), fenceFd, std::move(fd0), std::move(fd1)); jobs->cond.broadcast(); } void setDataspace(android_dataspace dataspace) { Mutexed::Locked jobs(mJobs); ColorUtils::convertDataSpaceToV0(dataspace); jobs->configUpdate.emplace_back(new C2StreamDataSpaceInfo::input(0u, dataspace)); int32_t standard; int32_t transfer; int32_t range; ColorUtils::getColorConfigFromDataSpace(dataspace, &range, &standard, &transfer); std::unique_ptr colorAspects = std::make_unique(0u); if (C2Mapper::map(standard, &colorAspects->primaries, &colorAspects->matrix) && C2Mapper::map(transfer, &colorAspects->transfer) && C2Mapper::map(range, &colorAspects->range)) { jobs->configUpdate.push_back(std::move(colorAspects)); } } void setPriority(int priority) { androidSetThreadPriority(getTid(), priority); } protected: bool threadLoop() override { constexpr nsecs_t kIntervalNs = nsecs_t(10) * 1000 * 1000; // 10ms constexpr nsecs_t kWaitNs = kIntervalNs * 2; for (int i = 0; i < 2; ++i) { Mutexed::Locked jobs(mJobs); nsecs_t nowNs = systemTime(); bool queued = false; for (auto it = jobs->queues.begin(); it != jobs->queues.end(); ) { Queue &queue = it->second; if (queue.workList.empty() || (queue.lastQueuedTimestampNs != 0 && nowNs - queue.lastQueuedTimestampNs < kIntervalNs)) { ++it; continue; } std::shared_ptr comp = queue.component.lock(); if (!comp) { it = jobs->queues.erase(it); continue; } std::list> items; std::vector fenceFds; std::vector uniqueFds; while (!queue.workList.empty()) { items.push_back(std::move(queue.workList.front().work)); fenceFds.push_back(queue.workList.front().fenceFd); uniqueFds.push_back(std::move(queue.workList.front().fd0)); uniqueFds.push_back(std::move(queue.workList.front().fd1)); queue.workList.pop_front(); } for (const std::unique_ptr ¶m : jobs->configUpdate) { items.front()->input.configUpdate.emplace_back(C2Param::Copy(*param)); } jobs.unlock(); for (int fenceFd : fenceFds) { sp fence(new Fence(fenceFd)); fence->waitForever(LOG_TAG); } queue.lastQueuedTimestampNs = nowNs; comp->queue(&items); for (android::base::unique_fd &ufd : uniqueFds) { (void)ufd.release(); } jobs.lock(); it = jobs->queues.upper_bound(comp); queued = true; } if (queued) { jobs->configUpdate.clear(); return true; } if (i == 0) { jobs.waitForConditionRelative(jobs->cond, kWaitNs); } } return true; } private: struct WorkFence { WorkFence(std::unique_ptr &&w, int fd) : work(std::move(w)), fenceFd(fd) {} WorkFence( std::unique_ptr &&w, int fd, android::base::unique_fd &&uniqueFd0, android::base::unique_fd &&uniqueFd1) : work(std::move(w)), fenceFd(fd), fd0(std::move(uniqueFd0)), fd1(std::move(uniqueFd1)) {} std::unique_ptr work; int fenceFd; android::base::unique_fd fd0; android::base::unique_fd fd1; }; struct Queue { Queue(const std::shared_ptr &comp) : component(comp), lastQueuedTimestampNs(0) {} Queue(const Queue &) = delete; Queue &operator =(const Queue &) = delete; std::weak_ptr component; std::list workList; nsecs_t lastQueuedTimestampNs; }; struct Jobs { std::map, Queue, std::owner_less>> queues; std::vector> configUpdate; Condition cond; }; Mutexed mJobs; }; C2OMXNode::C2OMXNode(const std::shared_ptr &comp) : mComp(comp), mFrameIndex(0), mWidth(0), mHeight(0), mUsage(0), mAdjustTimestampGapUs(0), mFirstInputFrame(true), mQueueThread(new QueueThread) { android_fdsan_set_error_level(ANDROID_FDSAN_ERROR_LEVEL_WARN_ALWAYS); mQueueThread->run("C2OMXNode", PRIORITY_AUDIO); Mutexed::Locked ds(mDataspace); *ds = HAL_DATASPACE_UNKNOWN; } status_t C2OMXNode::freeNode() { mComp.reset(); android_fdsan_set_error_level(ANDROID_FDSAN_ERROR_LEVEL_WARN_ONCE); return mQueueThread->requestExitAndWait(); } status_t C2OMXNode::sendCommand(OMX_COMMANDTYPE cmd, OMX_S32 param) { if (cmd == OMX_CommandStateSet && param == OMX_StateLoaded) { // Reset first input frame so if C2OMXNode is recycled, the timestamp does not become // negative. This is a workaround for HW codecs that do not handle timestamp rollover. mFirstInputFrame = true; } return ERROR_UNSUPPORTED; } status_t C2OMXNode::getParameter(OMX_INDEXTYPE index, void *params, size_t size) { status_t err = ERROR_UNSUPPORTED; switch ((uint32_t)index) { case OMX_IndexParamConsumerUsageBits: { OMX_U32 *usage = (OMX_U32 *)params; *usage = mUsage; err = OK; break; } case OMX_IndexParamPortDefinition: { if (size < sizeof(OMX_PARAM_PORTDEFINITIONTYPE)) { return BAD_VALUE; } OMX_PARAM_PORTDEFINITIONTYPE *pDef = (OMX_PARAM_PORTDEFINITIONTYPE *)params; if (pDef->nPortIndex != kPortIndexInput) { break; } pDef->nBufferCountActual = 16; // WORKAROUND: having more slots improve performance while consuming // more memory. This is a temporary workaround to reduce memory for // larger-than-4K scenario. if (mWidth * mHeight > 4096 * 2340) { std::shared_ptr comp = mComp.lock(); C2PortActualDelayTuning::input inputDelay(0); C2ActualPipelineDelayTuning pipelineDelay(0); c2_status_t c2err = C2_NOT_FOUND; if (comp) { c2err = comp->query( {&inputDelay, &pipelineDelay}, {}, C2_DONT_BLOCK, nullptr); } if (c2err == C2_OK || c2err == C2_BAD_INDEX) { pDef->nBufferCountActual = 4; pDef->nBufferCountActual += (inputDelay ? inputDelay.value : 0u); pDef->nBufferCountActual += (pipelineDelay ? pipelineDelay.value : 0u); } } pDef->eDomain = OMX_PortDomainVideo; pDef->format.video.nFrameWidth = mWidth; pDef->format.video.nFrameHeight = mHeight; pDef->format.video.eColorFormat = OMX_COLOR_FormatAndroidOpaque; err = OK; break; } default: break; } return err; } status_t C2OMXNode::setParameter(OMX_INDEXTYPE index, const void *params, size_t size) { if (params == NULL) { return BAD_VALUE; } switch ((uint32_t)index) { case OMX_IndexParamMaxFrameDurationForBitrateControl: // handle max/fixed frame duration control if (size != sizeof(OMX_PARAM_U32TYPE)) { return BAD_VALUE; } // The incoming number is an int32_t contained in OMX_U32. mAdjustTimestampGapUs = (int32_t)((OMX_PARAM_U32TYPE*)params)->nU32; return OK; case OMX_IndexParamConsumerUsageBits: if (size != sizeof(OMX_U32)) { return BAD_VALUE; } mUsage = *((OMX_U32 *)params); return OK; } return ERROR_UNSUPPORTED; } status_t C2OMXNode::getConfig(OMX_INDEXTYPE index, void *config, size_t size) { (void)index; (void)config; (void)size; return ERROR_UNSUPPORTED; } status_t C2OMXNode::setConfig(OMX_INDEXTYPE index, const void *config, size_t size) { (void)index; (void)config; (void)size; return ERROR_UNSUPPORTED; } status_t C2OMXNode::setPortMode(OMX_U32 portIndex, IOMX::PortMode mode) { (void)portIndex; (void)mode; return ERROR_UNSUPPORTED; } status_t C2OMXNode::prepareForAdaptivePlayback( OMX_U32 portIndex, OMX_BOOL enable, OMX_U32 maxFrameWidth, OMX_U32 maxFrameHeight) { (void)portIndex; (void)enable; (void)maxFrameWidth; (void)maxFrameHeight; return ERROR_UNSUPPORTED; } status_t C2OMXNode::configureVideoTunnelMode( OMX_U32 portIndex, OMX_BOOL tunneled, OMX_U32 audioHwSync, native_handle_t **sidebandHandle) { (void)portIndex; (void)tunneled; (void)audioHwSync; *sidebandHandle = nullptr; return ERROR_UNSUPPORTED; } status_t C2OMXNode::getGraphicBufferUsage(OMX_U32 portIndex, OMX_U32* usage) { (void)portIndex; *usage = 0; return ERROR_UNSUPPORTED; } status_t C2OMXNode::setInputSurface(const sp &bufferSource) { c2_status_t err = GetCodec2PlatformAllocatorStore()->fetchAllocator( C2PlatformAllocatorStore::GRALLOC, &mAllocator); if (err != OK) { return UNKNOWN_ERROR; } mBufferSource = bufferSource; return OK; } status_t C2OMXNode::allocateSecureBuffer( OMX_U32 portIndex, size_t size, buffer_id *buffer, void **bufferData, sp *nativeHandle) { (void)portIndex; (void)size; (void)nativeHandle; *buffer = 0; *bufferData = nullptr; return ERROR_UNSUPPORTED; } status_t C2OMXNode::useBuffer( OMX_U32 portIndex, const OMXBuffer &omxBuf, buffer_id *buffer) { (void)portIndex; (void)omxBuf; *buffer = 0; return ERROR_UNSUPPORTED; } status_t C2OMXNode::freeBuffer(OMX_U32 portIndex, buffer_id buffer) { (void)portIndex; (void)buffer; return ERROR_UNSUPPORTED; } status_t C2OMXNode::fillBuffer( buffer_id buffer, const OMXBuffer &omxBuf, int fenceFd) { (void)buffer; (void)omxBuf; (void)fenceFd; return ERROR_UNSUPPORTED; } status_t C2OMXNode::emptyBuffer( buffer_id buffer, const OMXBuffer &omxBuf, OMX_U32 flags, OMX_TICKS timestamp, int fenceFd) { std::shared_ptr comp = mComp.lock(); if (!comp) { return NO_INIT; } uint32_t c2Flags = (flags & OMX_BUFFERFLAG_EOS) ? C2FrameData::FLAG_END_OF_STREAM : 0; std::shared_ptr block; android::base::unique_fd fd0, fd1; C2Handle *handle = nullptr; if (omxBuf.mBufferType == OMXBuffer::kBufferTypeANWBuffer && omxBuf.mGraphicBuffer != nullptr) { std::shared_ptr alloc; handle = WrapNativeCodec2GrallocHandle( omxBuf.mGraphicBuffer->handle, omxBuf.mGraphicBuffer->width, omxBuf.mGraphicBuffer->height, omxBuf.mGraphicBuffer->format, omxBuf.mGraphicBuffer->usage, omxBuf.mGraphicBuffer->stride); if (handle != nullptr) { // unique_fd takes ownership of the fds, we'll get warning if these // fds get closed by somebody else. Onwership will be released before // we return, so that the fds get closed as usually when this function // goes out of scope (when both items and block are gone). native_handle_t *nativeHandle = reinterpret_cast(handle); fd0.reset(nativeHandle->numFds > 0 ? nativeHandle->data[0] : -1); fd1.reset(nativeHandle->numFds > 1 ? nativeHandle->data[1] : -1); } c2_status_t err = mAllocator->priorGraphicAllocation(handle, &alloc); if (err != OK) { (void)fd0.release(); (void)fd1.release(); native_handle_close(handle); native_handle_delete(handle); return UNKNOWN_ERROR; } block = _C2BlockFactory::CreateGraphicBlock(alloc); } else if (!(flags & OMX_BUFFERFLAG_EOS)) { return BAD_VALUE; } std::unique_ptr work(new C2Work); work->input.flags = (C2FrameData::flags_t)c2Flags; work->input.ordinal.timestamp = timestamp; // WORKAROUND: adjust timestamp based on gapUs { work->input.ordinal.customOrdinal = timestamp; // save input timestamp if (mFirstInputFrame) { // grab timestamps on first frame mPrevInputTimestamp = timestamp; mPrevCodecTimestamp = timestamp; mFirstInputFrame = false; } else if (mAdjustTimestampGapUs > 0) { work->input.ordinal.timestamp = mPrevCodecTimestamp + c2_min((timestamp - mPrevInputTimestamp).peek(), mAdjustTimestampGapUs); } else if (mAdjustTimestampGapUs < 0) { work->input.ordinal.timestamp = mPrevCodecTimestamp - mAdjustTimestampGapUs; } mPrevInputTimestamp = work->input.ordinal.customOrdinal; mPrevCodecTimestamp = work->input.ordinal.timestamp; ALOGV("adjusting %lld to %lld (gap=%lld)", work->input.ordinal.customOrdinal.peekll(), work->input.ordinal.timestamp.peekll(), (long long)mAdjustTimestampGapUs); } work->input.ordinal.frameIndex = mFrameIndex++; work->input.buffers.clear(); if (block) { std::shared_ptr c2Buffer( new Buffer2D(block->share( C2Rect(block->width(), block->height()), ::C2Fence()))); work->input.buffers.push_back(c2Buffer); } work->worklets.clear(); work->worklets.emplace_back(new C2Worklet); mBufferIdsInUse.lock()->emplace(work->input.ordinal.frameIndex.peeku(), buffer); mQueueThread->queue(comp, fenceFd, std::move(work), std::move(fd0), std::move(fd1)); return OK; } status_t C2OMXNode::getExtensionIndex( const char *parameterName, OMX_INDEXTYPE *index) { (void)parameterName; *index = OMX_IndexMax; return ERROR_UNSUPPORTED; } status_t C2OMXNode::dispatchMessage(const omx_message& msg) { if (msg.type != omx_message::EVENT) { return ERROR_UNSUPPORTED; } if (msg.u.event_data.event != OMX_EventDataSpaceChanged) { return ERROR_UNSUPPORTED; } android_dataspace dataSpace = (android_dataspace)msg.u.event_data.data1; uint32_t pixelFormat = msg.u.event_data.data3; ALOGD("dataspace changed to %#x pixel format: %#x", dataSpace, pixelFormat); mQueueThread->setDataspace(dataSpace); Mutexed::Locked ds(mDataspace); *ds = dataSpace; return OK; } sp C2OMXNode::getSource() { return mBufferSource; } void C2OMXNode::setFrameSize(uint32_t width, uint32_t height) { mWidth = width; mHeight = height; } void C2OMXNode::onInputBufferDone(c2_cntr64_t index) { if (!mBufferSource) { ALOGD("Buffer source not set (index=%llu)", index.peekull()); return; } int32_t bufferId = 0; { decltype(mBufferIdsInUse)::Locked bufferIds(mBufferIdsInUse); auto it = bufferIds->find(index.peeku()); if (it == bufferIds->end()) { ALOGV("Untracked input index %llu (maybe already removed)", index.peekull()); return; } bufferId = it->second; (void)bufferIds->erase(it); } (void)mBufferSource->onInputBufferEmptied(bufferId, -1); } android_dataspace C2OMXNode::getDataspace() { return *mDataspace.lock(); } void C2OMXNode::setPriority(int priority) { mQueueThread->setPriority(priority); } } // namespace android