// Copyright 2023 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 ATRACE_TAG ATRACE_TAG_VIDEO #define LOG_TAG "DecodeComponent" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace android { namespace { // Mask against 30 bits to avoid (undefined) wraparound on signed integer. int32_t frameIndexToBitstreamId(c2_cntr64_t frameIndex) { return static_cast(frameIndex.peeku() & 0x3FFFFFFF); } bool parseCodedColorAspects(const C2ConstLinearBlock& input, std::optional codec, C2StreamColorAspectsInfo::input* codedAspects) { C2ReadView view = input.map().get(); NalParser::ColorAspects aspects; std::unique_ptr parser; if (codec == VideoCodec::H264) { parser = std::make_unique(view.data(), view.capacity()); } else if (codec == VideoCodec::HEVC) { parser = std::make_unique(view.data(), view.capacity()); } else { ALOGV("Unsupported codec for finding color aspects"); return false; } if (!parser->locateSPS()) { ALOGV("Couldn't find SPS"); return false; } if (!parser->findCodedColorAspects(&aspects)) { ALOGV("Couldn't find color description in SPS"); return false; } // Convert ISO color aspects to ColorUtils::ColorAspects. ColorAspects colorAspects; ColorUtils::convertIsoColorAspectsToCodecAspects( aspects.primaries, aspects.transfer, aspects.coeffs, aspects.fullRange, colorAspects); ALOGV("Parsed ColorAspects from bitstream: (R:%d, P:%d, M:%d, T:%d)", colorAspects.mRange, colorAspects.mPrimaries, colorAspects.mMatrixCoeffs, colorAspects.mTransfer); // Map ColorUtils::ColorAspects to C2StreamColorAspectsInfo::input parameter. if (!C2Mapper::map(colorAspects.mPrimaries, &codedAspects->primaries)) { codedAspects->primaries = C2Color::PRIMARIES_UNSPECIFIED; } if (!C2Mapper::map(colorAspects.mRange, &codedAspects->range)) { codedAspects->range = C2Color::RANGE_UNSPECIFIED; } if (!C2Mapper::map(colorAspects.mMatrixCoeffs, &codedAspects->matrix)) { codedAspects->matrix = C2Color::MATRIX_UNSPECIFIED; } if (!C2Mapper::map(colorAspects.mTransfer, &codedAspects->transfer)) { codedAspects->transfer = C2Color::TRANSFER_UNSPECIFIED; } return true; } bool isWorkDone(const C2Work& work) { const int32_t bitstreamId = frameIndexToBitstreamId(work.input.ordinal.frameIndex); // Exception: EOS work should be processed by reportEOSWork(). // Always return false here no matter the work is actually done. if (work.input.flags & C2FrameData::FLAG_END_OF_STREAM) return false; // Work is done when all conditions meet: // 1. mDecoder has released the work's input buffer. // 2. mDecoder has returned the work's output buffer in normal case, // or the input buffer is CSD, or we decide to drop the frame. bool inputReleased = (work.input.buffers.front() == nullptr); bool outputReturned = !work.worklets.front()->output.buffers.empty(); bool ignoreOutput = (work.input.flags & C2FrameData::FLAG_CODEC_CONFIG) || (work.worklets.front()->output.flags & C2FrameData::FLAG_DROP_FRAME); ALOGV("work(%d): inputReleased: %d, outputReturned: %d, ignoreOutput: %d", bitstreamId, inputReleased, outputReturned, ignoreOutput); return inputReleased && (outputReturned || ignoreOutput); } bool isNoShowFrameWork(const C2Work& work, const C2WorkOrdinalStruct& currOrdinal) { // We consider Work contains no-show frame when all conditions meet: // 1. Work's ordinal is smaller than current ordinal. // 2. Work's output buffer is not returned. // 3. Work is not EOS, CSD, or marked with dropped frame. bool smallOrdinal = (work.input.ordinal.timestamp < currOrdinal.timestamp) && (work.input.ordinal.frameIndex < currOrdinal.frameIndex); bool outputReturned = !work.worklets.front()->output.buffers.empty(); bool specialWork = (work.input.flags & C2FrameData::FLAG_END_OF_STREAM) || (work.input.flags & C2FrameData::FLAG_CODEC_CONFIG) || (work.worklets.front()->output.flags & C2FrameData::FLAG_DROP_FRAME); return smallOrdinal && !outputReturned && !specialWork; } } // namespace DecodeComponent::DecodeComponent(uint32_t debugStreamId, const std::string& name, c2_node_id_t id, const std::shared_ptr& intfImpl) : mDebugStreamId(debugStreamId), mIntfImpl(intfImpl), mIntf(std::make_shared>(name.c_str(), id, mIntfImpl)) { ALOGV("%s(%s)", __func__, name.c_str()); mIsSecure = name.find(".secure") != std::string::npos; } DecodeComponent::~DecodeComponent() { ALOGV("%s()", __func__); if (mDecoderThread.IsRunning() && !mDecoderTaskRunner->RunsTasksInCurrentSequence()) { mDecoderTaskRunner->PostTask(FROM_HERE, ::base::BindOnce(&DecodeComponent::releaseTask, mWeakThis)); mDecoderThread.Stop(); } ALOGV("%s() done", __func__); } c2_status_t DecodeComponent::start() { ALOGV("%s()", __func__); std::lock_guard lock(mStartStopLock); auto currentState = mComponentState.load(); if (currentState != ComponentState::STOPPED) { ALOGE("Could not start at %s state", ComponentStateToString(currentState)); return C2_BAD_STATE; } if (!mDecoderThread.Start()) { ALOGE("Decoder thread failed to start."); return C2_CORRUPTED; } mDecoderTaskRunner = mDecoderThread.task_runner(); mWeakThis = mWeakThisFactory.GetWeakPtr(); c2_status_t status = C2_CORRUPTED; ::base::WaitableEvent done; mDecoderTaskRunner->PostTask( FROM_HERE, ::base::BindOnce(&DecodeComponent::startTask, mWeakThis, ::base::Unretained(&status), ::base::Unretained(&done))); done.Wait(); if (status == C2_OK) mComponentState.store(ComponentState::RUNNING); return status; } std::unique_ptr DecodeComponent::getVideoFramePool(const ui::Size& size, HalPixelFormat pixelFormat, size_t numBuffers) { ALOGV("%s()", __func__); ALOG_ASSERT(mDecoderTaskRunner->RunsTasksInCurrentSequence()); auto sharedThis = weak_from_this().lock(); if (sharedThis == nullptr) { ALOGE("%s(): DecodeComponent instance is destroyed.", __func__); return nullptr; } // (b/157113946): Prevent malicious dynamic resolution change exhausts system memory. constexpr int kMaximumSupportedArea = 4096 * 4096; if (getArea(size).value_or(INT_MAX) > kMaximumSupportedArea) { ALOGE("The output size (%dx%d) is larger than supported size (4096x4096)", size.width, size.height); reportError(C2_BAD_VALUE); return nullptr; } // Get block pool ID configured from the client. auto poolId = mIntfImpl->getBlockPoolId(); ALOGI("Using C2BlockPool ID = %" PRIu64 " for allocating output buffers", poolId); std::shared_ptr blockPool; auto status = GetCodec2BlockPool(poolId, std::move(sharedThis), &blockPool); if (status != C2_OK) { ALOGE("Graphic block allocator is invalid: %d", status); reportError(status); return nullptr; } return VideoFramePool::Create(std::move(blockPool), numBuffers, size, pixelFormat, mIsSecure, mDecoderTaskRunner); } c2_status_t DecodeComponent::stop() { ALOGV("%s()", __func__); std::lock_guard lock(mStartStopLock); auto currentState = mComponentState.load(); if (currentState != ComponentState::RUNNING && currentState != ComponentState::ERROR) { ALOGE("Could not stop at %s state", ComponentStateToString(currentState)); return C2_BAD_STATE; } if (mDecoderThread.IsRunning()) { mDecoderTaskRunner->PostTask(FROM_HERE, ::base::BindOnce(&DecodeComponent::stopTask, mWeakThis)); mDecoderThread.Stop(); mDecoderTaskRunner = nullptr; } mComponentState.store(ComponentState::STOPPED); return C2_OK; } void DecodeComponent::stopTask() { ATRACE_CALL(); ALOGV("%s()", __func__); ALOG_ASSERT(mDecoderTaskRunner->RunsTasksInCurrentSequence()); reportAbandonedWorks(); mIsDraining = false; releaseTask(); } c2_status_t DecodeComponent::reset() { ALOGV("%s()", __func__); auto currentState = mComponentState.load(); if (currentState == ComponentState::STOPPED) return C2_OK; return stop(); } c2_status_t DecodeComponent::release() { ALOGV("%s()", __func__); std::lock_guard lock(mStartStopLock); if (mDecoderThread.IsRunning()) { mDecoderTaskRunner->PostTask(FROM_HERE, ::base::BindOnce(&DecodeComponent::releaseTask, mWeakThis)); mDecoderThread.Stop(); mDecoderTaskRunner = nullptr; } mComponentState.store(ComponentState::RELEASED); return C2_OK; } void DecodeComponent::releaseTask() { ATRACE_CALL(); ALOGV("%s()", __func__); ALOG_ASSERT(mDecoderTaskRunner->RunsTasksInCurrentSequence()); mWeakThisFactory.InvalidateWeakPtrs(); mDecoder = nullptr; } c2_status_t DecodeComponent::setListener_vb(const std::shared_ptr& listener, c2_blocking_t mayBlock) { ALOGV("%s()", __func__); auto currentState = mComponentState.load(); if (currentState == ComponentState::RELEASED || (currentState == ComponentState::RUNNING && listener)) { ALOGE("Could not set listener at %s state", ComponentStateToString(currentState)); return C2_BAD_STATE; } if (currentState == ComponentState::RUNNING && mayBlock != C2_MAY_BLOCK) { ALOGE("Could not set listener at %s state non-blocking", ComponentStateToString(currentState)); return C2_BLOCKING; } // If the decoder thread is not running it's safe to update the listener directly. if (!mDecoderThread.IsRunning()) { mListener = listener; return C2_OK; } ::base::WaitableEvent done; mDecoderTaskRunner->PostTask( FROM_HERE, ::base::Bind(&DecodeComponent::setListenerTask, mWeakThis, listener, &done)); done.Wait(); return C2_OK; } void DecodeComponent::setListenerTask(const std::shared_ptr& listener, ::base::WaitableEvent* done) { ALOGV("%s()", __func__); ALOG_ASSERT(mDecoderTaskRunner->RunsTasksInCurrentSequence()); mListener = listener; done->Signal(); } c2_status_t DecodeComponent::queue_nb(std::list>* const items) { ALOGV("%s()", __func__); auto currentState = mComponentState.load(); if (currentState != ComponentState::RUNNING) { ALOGE("Could not queue at state: %s", ComponentStateToString(currentState)); return C2_BAD_STATE; } while (!items->empty()) { if (ATRACE_ENABLED()) { const std::string atraceLabel = ::base::StringPrintf("#%u C2Work", mDebugStreamId); ATRACE_ASYNC_BEGIN(atraceLabel.c_str(), items->front()->input.ordinal.frameIndex.peekull()); } mDecoderTaskRunner->PostTask(FROM_HERE, ::base::BindOnce(&DecodeComponent::queueTask, mWeakThis, std::move(items->front()))); items->pop_front(); } return C2_OK; } void DecodeComponent::queueTask(std::unique_ptr work) { ATRACE_CALL(); ALOGV("%s(): flags=0x%x, index=%llu, timestamp=%llu", __func__, work->input.flags, work->input.ordinal.frameIndex.peekull(), work->input.ordinal.timestamp.peekull()); ALOG_ASSERT(mDecoderTaskRunner->RunsTasksInCurrentSequence()); if (work->worklets.size() != 1u || work->input.buffers.size() > 1u) { ALOGE("Invalid work: worklets.size()=%zu, input.buffers.size()=%zu", work->worklets.size(), work->input.buffers.size()); work->result = C2_CORRUPTED; reportWork(std::move(work)); return; } work->worklets.front()->output.flags = static_cast(0); work->worklets.front()->output.buffers.clear(); work->worklets.front()->output.ordinal = work->input.ordinal; if (work->input.buffers.empty()) { // Client may queue a work with no input buffer for either it's EOS or empty CSD, otherwise // every work must have one input buffer. if ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) == 0 && (work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) == 0) { ALOGE("Invalid work: work with no input buffer should be EOS or CSD."); reportError(C2_BAD_VALUE); return; } // Emplace a nullptr to unify the check for work done. ALOGV("Got a work with no input buffer! Emplace a nullptr inside."); work->input.buffers.emplace_back(nullptr); } mPendingWorks.push(std::move(work)); pumpPendingWorks(); } void DecodeComponent::pumpPendingWorks() { ATRACE_CALL(); ALOGV("%s()", __func__); ALOG_ASSERT(mDecoderTaskRunner->RunsTasksInCurrentSequence()); auto currentState = mComponentState.load(); if (currentState != ComponentState::RUNNING) { ALOGW("Could not pump C2Work at state: %s", ComponentStateToString(currentState)); return; } while (!mPendingWorks.empty() && !mIsDraining) { std::unique_ptr pendingWork(std::move(mPendingWorks.front())); mPendingWorks.pop(); const int32_t bitstreamId = frameIndexToBitstreamId(pendingWork->input.ordinal.frameIndex); const bool isCSDWork = pendingWork->input.flags & C2FrameData::FLAG_CODEC_CONFIG; const bool isEmptyWork = pendingWork->input.buffers.front() == nullptr; const bool isEOSWork = pendingWork->input.flags & C2FrameData::FLAG_END_OF_STREAM; const C2Work* work = pendingWork.get(); ALOGV("Process C2Work bitstreamId=%d isCSDWork=%d, isEmptyWork=%d", bitstreamId, isCSDWork, isEmptyWork); auto res = mWorksAtDecoder.insert(std::make_pair(bitstreamId, std::move(pendingWork))); ALOGW_IF(!res.second, "We already inserted bitstreamId %d to decoder?", bitstreamId); if (!isEmptyWork) { if (isCSDWork) { processCSDWork(bitstreamId, work); } else { processWork(bitstreamId, work); } } if (isEOSWork) { mDecoder->drain(::base::BindOnce(&DecodeComponent::onDrainDone, mWeakThis)); mIsDraining = true; } // Directly report the empty CSD work as finished. if (isCSDWork && isEmptyWork) reportWorkIfFinished(bitstreamId); } } void DecodeComponent::processCSDWork(const int32_t bitstreamId, const C2Work* work) { // If input.buffers is not empty, the buffer should have meaningful content inside. C2ConstLinearBlock linearBlock = work->input.buffers.front()->data().linearBlocks().front(); ALOG_ASSERT(linearBlock.size() > 0u, "Input buffer of work(%d) is empty.", bitstreamId); if (mIntfImpl->getVideoCodec() == VideoCodec::VP9) { // The VP9 decoder does not support and does not need the Codec Specific Data (CSD): // https://www.webmproject.org/docs/container/#vp9-codec-feature-metadata-codecprivate. // The most of its content (profile, level, bit depth and chroma subsampling) // can be extracted directly from VP9 bitstream. Ignore CSD if it was passed. reportWorkIfFinished(bitstreamId); return; } else if ((!mIsSecure && mIntfImpl->getVideoCodec() == VideoCodec::H264) || mIntfImpl->getVideoCodec() == VideoCodec::HEVC) { // Try to parse color aspects from bitstream for CSD work of non-secure H264 codec or HEVC // codec (HEVC will only be CENCv3 which is parseable for secure). C2StreamColorAspectsInfo::input codedAspects = {0u}; if (parseCodedColorAspects(linearBlock, mIntfImpl->getVideoCodec(), &codedAspects)) { std::vector> failures; c2_status_t status = mIntfImpl->config({&codedAspects}, C2_MAY_BLOCK, &failures); if (status != C2_OK) { ALOGE("Failed to config color aspects to interface: %d", status); reportError(status); return; } // Record current frame index, color aspects should be updated only for output // buffers whose frame indices are not less than this one. mPendingColorAspectsChange = true; mPendingColorAspectsChangeFrameIndex = work->input.ordinal.frameIndex.peeku(); } } processWorkBuffer(bitstreamId, linearBlock); } void DecodeComponent::processWork(const int32_t bitstreamId, const C2Work* work) { // If input.buffers is not empty, the buffer should have meaningful content inside. C2ConstLinearBlock linearBlock = work->input.buffers.front()->data().linearBlocks().front(); ALOG_ASSERT(linearBlock.size() > 0u, "Input buffer of work(%d) is empty.", bitstreamId); processWorkBuffer(bitstreamId, linearBlock); } void DecodeComponent::processWorkBuffer(const int32_t bitstreamId, const C2ConstLinearBlock& linearBlock) { std::unique_ptr buffer = std::make_unique( bitstreamId, linearBlock, linearBlock.offset(), linearBlock.size()); if (!buffer) { reportError(C2_CORRUPTED); return; } mDecoder->decode(std::move(buffer), ::base::BindOnce(&DecodeComponent::onDecodeDone, mWeakThis, bitstreamId)); } void DecodeComponent::onDecodeDone(int32_t bitstreamId, VideoDecoder::DecodeStatus status) { ATRACE_CALL(); ALOGV("%s(bitstreamId=%d, status=%s)", __func__, bitstreamId, VideoDecoder::DecodeStatusToString(status)); ALOG_ASSERT(mDecoderTaskRunner->RunsTasksInCurrentSequence()); auto it = mWorksAtDecoder.find(bitstreamId); ALOG_ASSERT(it != mWorksAtDecoder.end()); C2Work* work = it->second.get(); switch (status) { case VideoDecoder::DecodeStatus::kAborted: work->input.buffers.front().reset(); work->worklets.front()->output.flags = static_cast( work->worklets.front()->output.flags & C2FrameData::FLAG_DROP_FRAME); mOutputBitstreamIds.push(bitstreamId); pumpReportWork(); return; case VideoDecoder::DecodeStatus::kError: reportError(C2_CORRUPTED); return; case VideoDecoder::DecodeStatus::kOk: // Release the input buffer. work->input.buffers.front().reset(); // CSD Work doesn't have output buffer, the corresponding onOutputFrameReady() won't be // called. Push the bitstreamId here. if (work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) mOutputBitstreamIds.push(bitstreamId); pumpReportWork(); return; } } void DecodeComponent::onOutputFrameReady(std::unique_ptr frame) { ALOGV("%s(bitstreamId=%d)", __func__, frame->getBitstreamId()); ALOG_ASSERT(mDecoderTaskRunner->RunsTasksInCurrentSequence()); const int32_t bitstreamId = frame->getBitstreamId(); auto it = mWorksAtDecoder.find(bitstreamId); if (it == mWorksAtDecoder.end()) { ALOGE("Work with bitstreamId=%d not found, already abandoned?", bitstreamId); reportError(C2_CORRUPTED); return; } C2Work* work = it->second.get(); C2ConstGraphicBlock constBlock = std::move(frame)->getGraphicBlock(); std::shared_ptr buffer = C2Buffer::CreateGraphicBuffer(std::move(constBlock)); if (mPendingColorAspectsChange && work->input.ordinal.frameIndex.peeku() >= mPendingColorAspectsChangeFrameIndex) { mIntfImpl->queryColorAspects(&mCurrentColorAspects); mPendingColorAspectsChange = false; } if (mCurrentColorAspects) { buffer->setInfo(mCurrentColorAspects); } work->worklets.front()->output.buffers.emplace_back(std::move(buffer)); // Check no-show frame by timestamps for VP8/VP9 cases before reporting the current work. if (mIntfImpl->getVideoCodec() == VideoCodec::VP8 || mIntfImpl->getVideoCodec() == VideoCodec::VP9) { detectNoShowFrameWorksAndReportIfFinished(work->input.ordinal); } mOutputBitstreamIds.push(bitstreamId); pumpReportWork(); } void DecodeComponent::detectNoShowFrameWorksAndReportIfFinished( const C2WorkOrdinalStruct& currOrdinal) { ATRACE_CALL(); ALOGV("%s()", __func__); ALOG_ASSERT(mDecoderTaskRunner->RunsTasksInCurrentSequence()); std::vector noShowFrameBitstreamIds; for (auto& kv : mWorksAtDecoder) { const int32_t bitstreamId = kv.first; const C2Work* work = kv.second.get(); // A work in mWorksAtDecoder would be considered to have no-show frame if there is no // corresponding output buffer returned while the one of the work with latter timestamp is // already returned. (VD is outputted in display order.) if (isNoShowFrameWork(*work, currOrdinal)) { work->worklets.front()->output.flags = C2FrameData::FLAG_DROP_FRAME; // We need to call reportWorkIfFinished() for all detected no-show frame works. However, // we should do it after the detection loop since reportWorkIfFinished() may erase // entries in |mWorksAtDecoder|. noShowFrameBitstreamIds.push_back(bitstreamId); ALOGV("Detected no-show frame work index=%llu timestamp=%llu", work->input.ordinal.frameIndex.peekull(), work->input.ordinal.timestamp.peekull()); } } // Try to report works with no-show frame. for (const int32_t bitstreamId : noShowFrameBitstreamIds) reportWorkIfFinished(bitstreamId); } void DecodeComponent::pumpReportWork() { ATRACE_CALL(); ALOGV("%s()", __func__); ALOG_ASSERT(mDecoderTaskRunner->RunsTasksInCurrentSequence()); while (!mOutputBitstreamIds.empty()) { if (!reportWorkIfFinished(mOutputBitstreamIds.front())) break; mOutputBitstreamIds.pop(); } } bool DecodeComponent::reportWorkIfFinished(int32_t bitstreamId) { ATRACE_CALL(); ALOGV("%s(bitstreamId = %d)", __func__, bitstreamId); ALOG_ASSERT(mDecoderTaskRunner->RunsTasksInCurrentSequence()); // EOS work will not be reported here. reportEOSWork() does it. if (mIsDraining && mWorksAtDecoder.size() == 1u) { ALOGV("work(bitstreamId = %d) is EOS Work.", bitstreamId); return false; } auto it = mWorksAtDecoder.find(bitstreamId); if (it == mWorksAtDecoder.end()) { ALOGI("work(bitstreamId = %d) is dropped, skip.", bitstreamId); return true; } if (!isWorkDone(*(it->second))) { ALOGV("work(bitstreamId = %d) is not done yet.", bitstreamId); return false; } std::unique_ptr work = std::move(it->second); mWorksAtDecoder.erase(it); work->result = C2_OK; work->workletsProcessed = static_cast(work->worklets.size()); // A work with neither flags nor output buffer would be treated as no-corresponding // output by C2 framework, and regain pipeline capacity immediately. if (work->worklets.front()->output.flags & C2FrameData::FLAG_DROP_FRAME) work->worklets.front()->output.flags = static_cast(0); return reportWork(std::move(work)); } bool DecodeComponent::reportEOSWork() { ATRACE_CALL(); ALOGV("%s()", __func__); ALOG_ASSERT(mDecoderTaskRunner->RunsTasksInCurrentSequence()); const auto it = std::find_if(mWorksAtDecoder.begin(), mWorksAtDecoder.end(), [](const auto& kv) { return kv.second->input.flags & C2FrameData::FLAG_END_OF_STREAM; }); if (it == mWorksAtDecoder.end()) { ALOGE("Failed to find EOS work."); return false; } std::unique_ptr eosWork(std::move(it->second)); mWorksAtDecoder.erase(it); eosWork->result = C2_OK; eosWork->workletsProcessed = static_cast(eosWork->worklets.size()); eosWork->worklets.front()->output.flags = C2FrameData::FLAG_END_OF_STREAM; if (!eosWork->input.buffers.empty()) eosWork->input.buffers.front().reset(); if (!mWorksAtDecoder.empty()) { ALOGW("There are remaining works except EOS work. abandon them."); for (const auto& kv : mWorksAtDecoder) { ALOGW("bitstreamId(%d) => Work index=%llu, timestamp=%llu", kv.first, kv.second->input.ordinal.frameIndex.peekull(), kv.second->input.ordinal.timestamp.peekull()); } reportAbandonedWorks(); } return reportWork(std::move(eosWork)); } bool DecodeComponent::reportWork(std::unique_ptr work) { ATRACE_CALL(); ALOGV("%s(work=%llu)", __func__, work->input.ordinal.frameIndex.peekull()); ALOG_ASSERT(mDecoderTaskRunner->RunsTasksInCurrentSequence()); if (!mListener) { ALOGE("mListener is nullptr, setListener_vb() not called?"); return false; } if (ATRACE_ENABLED()) { const std::string atraceLabel = ::base::StringPrintf("#%u C2Work", mDebugStreamId); ATRACE_ASYNC_END(atraceLabel.c_str(), work->input.ordinal.frameIndex.peekull()); } std::list> finishedWorks; finishedWorks.emplace_back(std::move(work)); mListener->onWorkDone_nb(weak_from_this(), std::move(finishedWorks)); return true; } c2_status_t DecodeComponent::flush_sm(flush_mode_t mode, std::list>* const /* flushedWork */) { ATRACE_CALL(); ALOGV("%s()", __func__); auto currentState = mComponentState.load(); if (currentState != ComponentState::RUNNING) { ALOGE("Could not flush at state: %s", ComponentStateToString(currentState)); return C2_BAD_STATE; } if (mode != FLUSH_COMPONENT) { return C2_OMITTED; // Tunneling is not supported by now } mDecoderTaskRunner->PostTask(FROM_HERE, ::base::BindOnce(&DecodeComponent::flushTask, mWeakThis)); return C2_OK; } void DecodeComponent::flushTask() { ATRACE_CALL(); ALOGV("%s()", __func__); ALOG_ASSERT(mDecoderTaskRunner->RunsTasksInCurrentSequence()); mDecoder->flush(); reportAbandonedWorks(); // Pending EOS work will be abandoned here due to component flush if any. mIsDraining = false; } void DecodeComponent::reportAbandonedWorks() { ALOGV("%s()", __func__); ALOG_ASSERT(mDecoderTaskRunner->RunsTasksInCurrentSequence()); std::list> abandonedWorks; while (!mPendingWorks.empty()) { abandonedWorks.emplace_back(std::move(mPendingWorks.front())); mPendingWorks.pop(); } for (auto& kv : mWorksAtDecoder) { abandonedWorks.emplace_back(std::move(kv.second)); } mWorksAtDecoder.clear(); for (auto& work : abandonedWorks) { // TODO: correlate the definition of flushed work result to framework. work->result = C2_NOT_FOUND; // When the work is abandoned, buffer in input.buffers shall reset by component. if (!work->input.buffers.empty()) { work->input.buffers.front().reset(); } if (ATRACE_ENABLED()) { const std::string atraceLabel = ::base::StringPrintf("#%u C2Work", mDebugStreamId); ATRACE_ASYNC_END(atraceLabel.c_str(), work->input.ordinal.frameIndex.peekull()); } } if (!abandonedWorks.empty()) { if (!mListener) { ALOGE("mListener is nullptr, setListener_vb() not called?"); return; } mListener->onWorkDone_nb(weak_from_this(), std::move(abandonedWorks)); } } c2_status_t DecodeComponent::drain_nb(drain_mode_t mode) { ALOGV("%s(mode=%u)", __func__, mode); auto currentState = mComponentState.load(); if (currentState != ComponentState::RUNNING) { ALOGE("Could not drain at state: %s", ComponentStateToString(currentState)); return C2_BAD_STATE; } switch (mode) { case DRAIN_CHAIN: return C2_OMITTED; // Tunneling is not supported. case DRAIN_COMPONENT_NO_EOS: return C2_OK; // Do nothing special. case DRAIN_COMPONENT_WITH_EOS: mDecoderTaskRunner->PostTask(FROM_HERE, ::base::BindOnce(&DecodeComponent::drainTask, mWeakThis)); return C2_OK; } } void DecodeComponent::drainTask() { ATRACE_CALL(); ALOGV("%s()", __func__); ALOG_ASSERT(mDecoderTaskRunner->RunsTasksInCurrentSequence()); if (!mPendingWorks.empty()) { ALOGV("Set EOS flag at last queued work."); auto& flags = mPendingWorks.back()->input.flags; flags = static_cast(flags | C2FrameData::FLAG_END_OF_STREAM); return; } if (!mWorksAtDecoder.empty()) { ALOGV("Drain the pending works at the decoder."); mDecoder->drain(::base::BindOnce(&DecodeComponent::onDrainDone, mWeakThis)); mIsDraining = true; } } void DecodeComponent::onDrainDone(VideoDecoder::DecodeStatus status) { ALOGV("%s(status=%s)", __func__, VideoDecoder::DecodeStatusToString(status)); ALOG_ASSERT(mDecoderTaskRunner->RunsTasksInCurrentSequence()); switch (status) { case VideoDecoder::DecodeStatus::kAborted: return; case VideoDecoder::DecodeStatus::kError: reportError(C2_CORRUPTED); return; case VideoDecoder::DecodeStatus::kOk: mIsDraining = false; if (!reportEOSWork()) { reportError(C2_CORRUPTED); return; } mDecoderTaskRunner->PostTask( FROM_HERE, ::base::BindOnce(&DecodeComponent::pumpPendingWorks, mWeakThis)); return; } } void DecodeComponent::reportError(c2_status_t error) { ALOGE("%s(error=%u)", __func__, static_cast(error)); ALOG_ASSERT(mDecoderTaskRunner->RunsTasksInCurrentSequence()); if (mComponentState.load() == ComponentState::ERROR) return; mComponentState.store(ComponentState::ERROR); if (!mListener) { ALOGE("mListener is nullptr, setListener_vb() not called?"); return; } mListener->onError_nb(weak_from_this(), static_cast(error)); } c2_status_t DecodeComponent::announce_nb(const std::vector& /* items */) { return C2_OMITTED; // Tunneling is not supported by now } std::shared_ptr DecodeComponent::intf() { return mIntf; } // static const char* DecodeComponent::ComponentStateToString(ComponentState state) { switch (state) { case ComponentState::STOPPED: return "STOPPED"; case ComponentState::RUNNING: return "RUNNING"; case ComponentState::RELEASED: return "RELEASED"; case ComponentState::ERROR: return "ERROR"; } } } // namespace android