| // Copyright 2017 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 "C2VDAComponent" |
| |
| #include <C2VDAAdaptor.h> |
| #define __C2_GENERATE_GLOBAL_VARS__ |
| #include <C2VDAComponent.h> |
| #include <C2VDASupport.h> |
| |
| #include <videodev2.h> |
| |
| #include <base/bind.h> |
| #include <base/bind_helpers.h> |
| |
| #include <media/stagefright/MediaDefs.h> |
| #include <utils/Log.h> |
| #include <utils/misc.h> |
| |
| #include <inttypes.h> |
| #include <algorithm> |
| |
| #define UNUSED(expr) \ |
| do { \ |
| (void)(expr); \ |
| } while (0) |
| |
| namespace android { |
| |
| namespace { |
| |
| // Get index from C2param object. Use index to identify the type of the parameter. |
| // Currently there is no wise way to get index from a parameter because index is private. |
| uint32_t restoreIndex(const C2Param* param) { |
| return (param->forStream() ? (0x02000000 | ((param->stream() << 17) & 0x01FE0000)) : 0) | |
| param->type(); |
| } |
| |
| // Helper function to allocate string type parameters. |
| template <class T> |
| std::unique_ptr<T> allocUniqueCstr(const char* cstr) { |
| size_t len = strlen(cstr); |
| std::unique_ptr<T> ptr = T::alloc_unique(len); |
| memcpy(ptr->m.mValue, cstr, len); |
| return ptr; |
| } |
| |
| template <class T> |
| std::unique_ptr<C2SettingResult> reportReadOnlyFailure(C2Param* c2Param) { |
| T* param = (T*)c2Param; |
| return std::unique_ptr<C2SettingResult>( |
| new C2SettingResult{C2SettingResult::READ_ONLY, |
| {C2ParamField(param, &T::mValue), nullptr /* supportedValues */}, |
| {} /* conflictedFields */}); |
| } |
| |
| template <class T> |
| std::unique_ptr<C2SettingResult> reportReadOnlyFlexFailure(C2Param* c2Param) { |
| T* param = (T*)c2Param; |
| return std::unique_ptr<C2SettingResult>( |
| new C2SettingResult{C2SettingResult::READ_ONLY, |
| {C2ParamField(param, &T::m), nullptr /* supportedValues */}, |
| {} /* conflictedFields */}); |
| } |
| |
| // Helper function to find int32_t value from C2Value::Primitive vector. |
| bool findInt32FromPrimitiveValues(const int32_t& v, const C2FieldSupportedValues& values) { |
| if (values.type == C2FieldSupportedValues::EMPTY) { |
| return false; |
| } |
| if (values.type == C2FieldSupportedValues::FLAGS) { |
| ALOGE("Type of field supported values should not be FLAGS."); |
| return false; |
| } |
| if (values.type == C2FieldSupportedValues::RANGE) { |
| // Only support min/max/step case only. |
| return v >= values.range.min.i32 && v <= values.range.max.i32 && |
| ((v - values.range.min.i32) % values.range.step.i32 == 0); |
| } |
| // if values.type == C2FieldSupportedValues::VALUES |
| return std::any_of(values.values.begin(), values.values.end(), |
| [v = v](const auto& value) { return value.i32 == v; }); |
| } |
| |
| // Helper function to find uint32_t value from C2Value::Primitive vector. |
| bool findUint32FromPrimitiveValues(const uint32_t& v, const C2FieldSupportedValues& values) { |
| if (values.type == C2FieldSupportedValues::EMPTY) { |
| return false; |
| } |
| if (values.type == C2FieldSupportedValues::FLAGS) { |
| ALOGE("Type of field supported values should not be FLAGS."); |
| return false; |
| } |
| if (values.type == C2FieldSupportedValues::RANGE) { |
| // Only support min/max/step case only. |
| return v >= values.range.min.u32 && v <= values.range.max.u32 && |
| ((v - values.range.min.u32) % values.range.step.u32 == 0); |
| } |
| // if values.type == C2FieldSupportedValues::VALUES |
| return std::any_of(values.values.begin(), values.values.end(), |
| [v = v](const auto& value) { return value.u32 == v; }); |
| } |
| |
| // Mask against 30 bits to avoid (undefined) wraparound on signed integer. |
| int32_t frameIndexToBitstreamId(uint64_t frameIndex) { |
| return static_cast<int32_t>(frameIndex & 0x3FFFFFFF); |
| } |
| |
| const C2String kH264DecoderName = "v4l2.h264.decode"; |
| const C2String kVP8DecoderName = "v4l2.vp8.decode"; |
| const C2String kVP9DecoderName = "v4l2.vp9.decode"; |
| |
| } // namespace |
| |
| C2VDAComponentIntf::C2VDAComponentIntf(C2String name, c2_node_id_t id) |
| : kName(name), |
| kId(id), |
| mInitStatus(C2_OK), |
| mDomainInfo(C2DomainVideo), |
| mOutputColorFormat(0u, kColorFormatYUV420Flexible), |
| mOutputPortMime(allocUniqueCstr<C2PortMimeConfig::output>(MEDIA_MIMETYPE_VIDEO_RAW)), |
| mOutputBlockPools(C2PortBlockPoolsTuning::output::alloc_unique({})) { |
| // TODO(johnylin): use factory function to determine whether V4L2 stream or slice API is. |
| uint32_t inputFormatFourcc; |
| if (name == kH264DecoderName) { |
| mInputPortMime = allocUniqueCstr<C2PortMimeConfig::input>(MEDIA_MIMETYPE_VIDEO_AVC); |
| inputFormatFourcc = V4L2_PIX_FMT_H264_SLICE; |
| } else if (name == kVP8DecoderName) { |
| mInputPortMime = allocUniqueCstr<C2PortMimeConfig::input>(MEDIA_MIMETYPE_VIDEO_VP8); |
| inputFormatFourcc = V4L2_PIX_FMT_VP8_FRAME; |
| } else if (name == kVP9DecoderName) { |
| mInputPortMime = allocUniqueCstr<C2PortMimeConfig::input>(MEDIA_MIMETYPE_VIDEO_VP9); |
| inputFormatFourcc = V4L2_PIX_FMT_VP9_FRAME; |
| } else { |
| ALOGE("Invalid component name: %s", name.c_str()); |
| mInitStatus = C2_BAD_VALUE; |
| return; |
| } |
| // Get supported profiles from VDA. |
| // TODO: re-think the suitable method of getting supported profiles for both pure Android and |
| // ARC++. |
| mSupportedProfiles = C2VDAAdaptor::GetSupportedProfiles(inputFormatFourcc); |
| if (mSupportedProfiles.empty()) { |
| ALOGE("No supported profile from input format: %u", inputFormatFourcc); |
| mInitStatus = C2_BAD_VALUE; |
| return; |
| } |
| |
| // Set default codec profile. |
| mInputCodecProfile.mValue = mSupportedProfiles[0].profile; |
| |
| auto minVideoSize = mSupportedProfiles[0].min_resolution; |
| auto maxVideoSize = mSupportedProfiles[0].max_resolution; |
| // Set default output video size. |
| mVideoSize.mWidth = minVideoSize.width(); |
| mVideoSize.mHeight = minVideoSize.height(); |
| // Set default max video size. |
| mMaxVideoSizeHint.mWidth = maxVideoSize.width(); |
| mMaxVideoSizeHint.mHeight = maxVideoSize.height(); |
| |
| for (const auto& supportedProfile : mSupportedProfiles) { |
| mSupportedCodecProfiles.push_back(supportedProfile.profile); |
| ALOGI("Get supported profile: profile=%d, min_res=%s, max_res=%s", supportedProfile.profile, |
| supportedProfile.min_resolution.ToString().c_str(), |
| supportedProfile.max_resolution.ToString().c_str()); |
| } |
| |
| auto insertParam = [& params = mParams](C2Param* param) { |
| params[restoreIndex(param)] = param; |
| }; |
| |
| insertParam(&mDomainInfo); |
| insertParam(&mOutputColorFormat); |
| insertParam(mInputPortMime.get()); |
| insertParam(mOutputPortMime.get()); |
| |
| insertParam(&mInputCodecProfile); |
| mSupportedValues.emplace(C2ParamField(&mInputCodecProfile, &C2StreamFormatConfig::mValue), |
| C2FieldSupportedValues(false, mSupportedCodecProfiles)); |
| |
| // TODO(johnylin): min/max resolution may change by chosen profile, we should dynamically change |
| // the supported values in the future. |
| insertParam(&mVideoSize); |
| mSupportedValues.emplace( |
| C2ParamField(&mVideoSize, &C2VideoSizeStreamInfo::mWidth), |
| C2FieldSupportedValues(minVideoSize.width(), maxVideoSize.width(), 16)); |
| mSupportedValues.emplace( |
| C2ParamField(&mVideoSize, &C2VideoSizeStreamInfo::mHeight), |
| C2FieldSupportedValues(minVideoSize.height(), maxVideoSize.height(), 16)); |
| |
| insertParam(&mMaxVideoSizeHint); |
| mSupportedValues.emplace( |
| C2ParamField(&mMaxVideoSizeHint, &C2MaxVideoSizeHintPortSetting::mWidth), |
| C2FieldSupportedValues(minVideoSize.width(), maxVideoSize.width(), 16)); |
| mSupportedValues.emplace( |
| C2ParamField(&mMaxVideoSizeHint, &C2MaxVideoSizeHintPortSetting::mHeight), |
| C2FieldSupportedValues(minVideoSize.height(), maxVideoSize.height(), 16)); |
| |
| insertParam(mOutputBlockPools.get()); |
| |
| mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(true, "_domain", &mDomainInfo)); |
| mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(false, "_output_color_format", |
| &mOutputColorFormat)); |
| mParamDescs.push_back( |
| std::make_shared<C2ParamDescriptor>(true, "_input_port_mime", mInputPortMime.get())); |
| mParamDescs.push_back( |
| std::make_shared<C2ParamDescriptor>(true, "_output_port_mime", mOutputPortMime.get())); |
| mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(false, "_input_codec_profile", |
| &mInputCodecProfile)); |
| mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(false, "_video_size", &mVideoSize)); |
| mParamDescs.push_back( |
| std::make_shared<C2ParamDescriptor>(false, "_max_video_size_hint", &mMaxVideoSizeHint)); |
| mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(false, "_output_block_pools", |
| mOutputBlockPools.get())); |
| } |
| |
| C2String C2VDAComponentIntf::getName() const { |
| return kName; |
| } |
| |
| c2_node_id_t C2VDAComponentIntf::getId() const { |
| return kId; |
| } |
| |
| c2_status_t C2VDAComponentIntf::query_vb( |
| const std::vector<C2Param* const>& stackParams, |
| const std::vector<C2Param::Index>& heapParamIndices, c2_blocking_t mayBlock, |
| std::vector<std::unique_ptr<C2Param>>* const heapParams) const { |
| UNUSED(mayBlock); |
| c2_status_t err = C2_OK; |
| for (C2Param* const param : stackParams) { |
| if (!param || !*param) { |
| continue; |
| } |
| |
| uint32_t index = restoreIndex(param); |
| C2Param* myParam = getParamByIndex(index); |
| if (!myParam || (myParam->size() != param->size())) { |
| param->invalidate(); |
| err = C2_BAD_INDEX; |
| continue; |
| } |
| |
| param->updateFrom(*myParam); |
| } |
| |
| // heapParams should not be nullptr if heapParamIndices is not empty. |
| CHECK(heapParamIndices.size() == 0 || heapParams); |
| for (const C2Param::Index index : heapParamIndices) { |
| C2Param* myParam = getParamByIndex(index); |
| if (myParam) { |
| heapParams->emplace_back(C2Param::Copy(*myParam)); |
| } else { |
| err = C2_BAD_INDEX; |
| } |
| } |
| |
| return err; |
| } |
| |
| c2_status_t C2VDAComponentIntf::config_vb( |
| const std::vector<C2Param* const>& params, c2_blocking_t mayBlock, |
| std::vector<std::unique_ptr<C2SettingResult>>* const failures) { |
| UNUSED(mayBlock); |
| c2_status_t err = C2_OK; |
| for (C2Param* const param : params) { |
| uint32_t index = restoreIndex(param); |
| C2Param* myParam = getParamByIndex(index); |
| if (!myParam) { |
| // C2_BAD_INDEX should be the lowest priority except for C2_OK. |
| err = (err == C2_OK) ? C2_BAD_INDEX : err; |
| continue; |
| } |
| |
| if (index == restoreIndex(&mDomainInfo)) { // read-only |
| failures->push_back(reportReadOnlyFailure<decltype(mDomainInfo)>(param)); |
| err = C2_BAD_VALUE; |
| continue; |
| } else if (index == restoreIndex(&mOutputColorFormat)) { // read-only |
| failures->push_back(reportReadOnlyFailure<decltype(mOutputColorFormat)>(param)); |
| err = C2_BAD_VALUE; |
| continue; |
| } else if (index == restoreIndex(mInputPortMime.get())) { // read-only |
| failures->push_back(reportReadOnlyFlexFailure< |
| std::remove_pointer<decltype(mInputPortMime.get())>::type>(param)); |
| err = C2_BAD_VALUE; |
| continue; |
| } else if (index == restoreIndex(mOutputPortMime.get())) { // read-only |
| failures->push_back(reportReadOnlyFlexFailure< |
| std::remove_pointer<decltype(mOutputPortMime.get())>::type>(param)); |
| err = C2_BAD_VALUE; |
| continue; |
| } else if (index == restoreIndex(&mInputCodecProfile)) { |
| std::unique_ptr<C2SettingResult> result = |
| validateUint32Config<decltype(mInputCodecProfile)>(param); |
| if (result) { |
| failures->push_back(std::move(result)); |
| err = C2_BAD_VALUE; |
| continue; |
| } |
| } else if (index == restoreIndex(&mVideoSize)) { |
| std::unique_ptr<C2SettingResult> result = |
| validateVideoSizeConfig<decltype(mVideoSize)>(param); |
| if (result) { |
| failures->push_back(std::move(result)); |
| err = C2_BAD_VALUE; |
| continue; |
| } |
| } else if (index == restoreIndex(&mMaxVideoSizeHint)) { |
| std::unique_ptr<C2SettingResult> result = |
| validateVideoSizeConfig<decltype(mMaxVideoSizeHint)>(param); |
| if (result) { |
| failures->push_back(std::move(result)); |
| err = C2_BAD_VALUE; |
| continue; |
| } |
| } else if (index == restoreIndex(mOutputBlockPools.get())) { |
| // setting output block pools |
| // TODO: add support for output-block-pools (this will be done when we move all |
| // config to shared ptr) |
| mOutputBlockPools.reset( |
| static_cast<C2PortBlockPoolsTuning::output*>(C2Param::Copy(*param).release())); |
| continue; |
| } |
| myParam->updateFrom(*param); |
| } |
| return err; |
| } |
| |
| c2_status_t C2VDAComponentIntf::createTunnel_sm(c2_node_id_t targetComponent) { |
| UNUSED(targetComponent); |
| return C2_OMITTED; // Tunneling is not supported by now |
| } |
| |
| c2_status_t C2VDAComponentIntf::releaseTunnel_sm(c2_node_id_t targetComponent) { |
| UNUSED(targetComponent); |
| return C2_OMITTED; // Tunneling is not supported by now |
| } |
| |
| c2_status_t C2VDAComponentIntf::querySupportedParams_nb( |
| std::vector<std::shared_ptr<C2ParamDescriptor>>* const params) const { |
| params->clear(); |
| params->insert(params->begin(), mParamDescs.begin(), mParamDescs.end()); |
| return C2_OK; |
| } |
| |
| c2_status_t C2VDAComponentIntf::querySupportedValues_vb( |
| std::vector<C2FieldSupportedValuesQuery>& fields, c2_blocking_t mayBlock) const { |
| UNUSED(mayBlock); |
| c2_status_t err = C2_OK; |
| for (auto& query : fields) { |
| if (mSupportedValues.count(query.field) == 0) { |
| query.status = C2_BAD_INDEX; |
| err = C2_BAD_INDEX; |
| continue; |
| } |
| query.status = C2_OK; |
| query.values = mSupportedValues.at(query.field); |
| } |
| return err; |
| } |
| |
| c2_status_t C2VDAComponentIntf::status() const { |
| return mInitStatus; |
| } |
| |
| C2Param* C2VDAComponentIntf::getParamByIndex(uint32_t index) const { |
| auto iter = mParams.find(index); |
| return (iter != mParams.end()) ? iter->second : nullptr; |
| } |
| |
| template <class T> |
| std::unique_ptr<C2SettingResult> C2VDAComponentIntf::validateVideoSizeConfig( |
| C2Param* c2Param) const { |
| T* videoSize = (T*)c2Param; |
| |
| C2ParamField fieldWidth(videoSize, &T::mWidth); |
| const C2FieldSupportedValues& widths = mSupportedValues.at(fieldWidth); |
| CHECK_EQ(widths.type, C2FieldSupportedValues::RANGE); |
| if (!findInt32FromPrimitiveValues(videoSize->mWidth, widths)) { |
| std::unique_ptr<C2SettingResult> result(new C2SettingResult{ |
| C2SettingResult::BAD_VALUE, |
| {fieldWidth, std::make_unique<C2FieldSupportedValues>( |
| widths.range.min, widths.range.max, widths.range.step)}, |
| {} /* conflicts */}); |
| return result; |
| } |
| |
| C2ParamField fieldHeight(videoSize, &T::mHeight); |
| const C2FieldSupportedValues& heights = mSupportedValues.at(fieldHeight); |
| CHECK_EQ(heights.type, C2FieldSupportedValues::RANGE); |
| if (!findInt32FromPrimitiveValues(videoSize->mHeight, heights)) { |
| std::unique_ptr<C2SettingResult> result(new C2SettingResult{ |
| C2SettingResult::BAD_VALUE, |
| {fieldHeight, std::make_unique<C2FieldSupportedValues>( |
| heights.range.min, heights.range.max, heights.range.step)}, |
| {} /* conflicts */}); |
| return result; |
| } |
| |
| return nullptr; |
| } |
| |
| template <class T> |
| std::unique_ptr<C2SettingResult> C2VDAComponentIntf::validateUint32Config(C2Param* c2Param) const { |
| T* config = (T*)c2Param; |
| |
| C2ParamField field(config, &T::mValue); |
| const C2FieldSupportedValues& configs = mSupportedValues.at(field); |
| if (!findUint32FromPrimitiveValues(config->mValue, configs)) { |
| std::unique_ptr<C2SettingResult> result(new C2SettingResult{ |
| C2SettingResult::BAD_VALUE, {field, nullptr}, {} /* conflicts */}); |
| if (configs.type == C2FieldSupportedValues::RANGE) { |
| result->field.values.reset(new C2FieldSupportedValues( |
| configs.range.min, configs.range.max, configs.range.step)); |
| } else if (configs.type == C2FieldSupportedValues::VALUES) { |
| result->field.values.reset(new C2FieldSupportedValues(false, configs.values)); |
| } else { |
| return nullptr; |
| } |
| return result; |
| } |
| |
| return nullptr; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| #define EXPECT_STATE_OR_RETURN_ON_ERROR(x) \ |
| do { \ |
| if (mComponentState == ComponentState::ERROR) return; \ |
| CHECK_EQ(mComponentState, ComponentState::x); \ |
| } while (0) |
| |
| #define EXPECT_RUNNING_OR_RETURN_ON_ERROR() \ |
| do { \ |
| if (mComponentState == ComponentState::ERROR) return; \ |
| CHECK_NE(mComponentState, ComponentState::UNINITIALIZED); \ |
| } while (0) |
| |
| class C2VDAGraphicBuffer : public C2Buffer { |
| public: |
| C2VDAGraphicBuffer(const std::shared_ptr<C2GraphicBlock>& block, |
| const base::Closure& releaseCB); |
| ~C2VDAGraphicBuffer() override; |
| |
| private: |
| base::Closure mReleaseCB; |
| }; |
| |
| C2VDAGraphicBuffer::C2VDAGraphicBuffer(const std::shared_ptr<C2GraphicBlock>& block, |
| const base::Closure& releaseCB) |
| : C2Buffer({block->share(C2Rect(block->width(), block->height()), C2Fence())}), |
| mReleaseCB(releaseCB) {} |
| |
| C2VDAGraphicBuffer::~C2VDAGraphicBuffer() { |
| if (!mReleaseCB.is_null()) { |
| mReleaseCB.Run(); |
| } |
| } |
| |
| C2VDAComponent::VideoFormat::VideoFormat(uint32_t pixelFormat, uint32_t minNumBuffers, |
| media::Size codedSize, media::Rect visibleRect) |
| : mPixelFormat(pixelFormat), |
| mMinNumBuffers(minNumBuffers), |
| mCodedSize(codedSize), |
| mVisibleRect(visibleRect) {} |
| |
| C2VDAComponent::C2VDAComponent(C2String name, c2_node_id_t id) |
| : mIntf(std::make_shared<C2VDAComponentIntf>(name, id)), |
| mThread("C2VDAComponentThread"), |
| mVDAInitResult(VideoDecodeAcceleratorAdaptor::Result::ILLEGAL_STATE), |
| mComponentState(ComponentState::UNINITIALIZED), |
| mColorFormat(0u), |
| mLastOutputTimestamp(-1), |
| mCodecProfile(media::VIDEO_CODEC_PROFILE_UNKNOWN), |
| mState(State::UNLOADED), |
| mWeakThisFactory(this) { |
| // TODO(johnylin): the client may need to know if init is failed. |
| if (mIntf->status() != C2_OK) { |
| ALOGE("Component interface init failed (err code = %d)", mIntf->status()); |
| return; |
| } |
| if (!mThread.Start()) { |
| ALOGE("Component thread failed to start."); |
| return; |
| } |
| mTaskRunner = mThread.task_runner(); |
| mTaskRunner->PostTask(FROM_HERE, base::Bind(&C2VDAComponent::onCreate, base::Unretained(this))); |
| mState = State::LOADED; |
| } |
| |
| C2VDAComponent::~C2VDAComponent() { |
| CHECK_EQ(mState, State::LOADED); |
| |
| if (mThread.IsRunning()) { |
| mTaskRunner->PostTask(FROM_HERE, |
| base::Bind(&C2VDAComponent::onDestroy, base::Unretained(this))); |
| mThread.Stop(); |
| } |
| } |
| |
| void C2VDAComponent::fetchParametersFromIntf() { |
| C2StreamFormatConfig::input codecProfile; |
| std::vector<C2Param* const> stackParams{&codecProfile}; |
| CHECK_EQ(mIntf->query_vb(stackParams, {}, C2_DONT_BLOCK, nullptr), C2_OK); |
| // The value should be guaranteed to be within media::VideoCodecProfile enum range by component |
| // interface. |
| mCodecProfile = static_cast<media::VideoCodecProfile>(codecProfile.mValue); |
| ALOGI("get parameter: mCodecProfile = %d", static_cast<int>(mCodecProfile)); |
| } |
| |
| void C2VDAComponent::onCreate() { |
| DCHECK(mTaskRunner->BelongsToCurrentThread()); |
| ALOGV("onCreate"); |
| mVDAAdaptor.reset(new C2VDAAdaptor()); |
| } |
| |
| void C2VDAComponent::onDestroy() { |
| DCHECK(mTaskRunner->BelongsToCurrentThread()); |
| ALOGV("onDestroy"); |
| if (mVDAAdaptor.get()) { |
| mVDAAdaptor->destroy(); |
| mVDAAdaptor.reset(nullptr); |
| } |
| } |
| |
| void C2VDAComponent::onStart(media::VideoCodecProfile profile, base::WaitableEvent* done) { |
| DCHECK(mTaskRunner->BelongsToCurrentThread()); |
| ALOGV("onStart"); |
| CHECK_EQ(mComponentState, ComponentState::UNINITIALIZED); |
| // TODO: Set secureMode value dynamically. |
| bool secureMode = false; |
| mVDAInitResult = mVDAAdaptor->initialize(profile, secureMode, this); |
| if (mVDAInitResult == VideoDecodeAcceleratorAdaptor::Result::SUCCESS) { |
| mComponentState = ComponentState::STARTED; |
| } |
| |
| done->Signal(); |
| } |
| |
| void C2VDAComponent::onQueueWork(std::unique_ptr<C2Work> work) { |
| DCHECK(mTaskRunner->BelongsToCurrentThread()); |
| ALOGV("onQueueWork"); |
| EXPECT_RUNNING_OR_RETURN_ON_ERROR(); |
| // It is illegal for client to put new works while component is still flushing. |
| CHECK_NE(mComponentState, ComponentState::FLUSHING); |
| mQueue.emplace(std::move(work)); |
| // TODO(johnylin): set a maximum size of mQueue and check if mQueue is already full. |
| |
| mTaskRunner->PostTask(FROM_HERE, |
| base::Bind(&C2VDAComponent::onDequeueWork, base::Unretained(this))); |
| } |
| |
| void C2VDAComponent::onDequeueWork() { |
| DCHECK(mTaskRunner->BelongsToCurrentThread()); |
| ALOGV("onDequeueWork"); |
| EXPECT_RUNNING_OR_RETURN_ON_ERROR(); |
| if (mQueue.empty()) { |
| return; |
| } |
| if (mComponentState == ComponentState::DRAINING) { |
| ALOGV("Temporarily stop dequeueing works since component is draining."); |
| return; |
| } |
| if (mComponentState != ComponentState::STARTED) { |
| ALOGE("Work queue should be empty if the component is not in STARTED state."); |
| return; |
| } |
| |
| // Dequeue a work from mQueue. |
| std::unique_ptr<C2Work> work(std::move(mQueue.front())); |
| mQueue.pop(); |
| |
| // Send input buffer to VDA for decode. |
| // Use frame_index as bitstreamId. |
| CHECK_EQ(work->input.buffers.size(), 1u); |
| C2ConstLinearBlock linearBlock = work->input.buffers.front()->data().linearBlocks().front(); |
| if (linearBlock.size() > 0) { |
| int32_t bitstreamId = frameIndexToBitstreamId(work->input.ordinal.frame_index); |
| sendInputBufferToAccelerator(linearBlock, bitstreamId); |
| } |
| |
| if (work->input.flags & C2BufferPack::FLAG_END_OF_STREAM) { |
| mVDAAdaptor->flush(); |
| mComponentState = ComponentState::DRAINING; |
| } |
| |
| // Put work to mPendingWork. |
| mPendingWorks.emplace_back(std::move(work)); |
| |
| if (!mQueue.empty()) { |
| mTaskRunner->PostTask(FROM_HERE, |
| base::Bind(&C2VDAComponent::onDequeueWork, base::Unretained(this))); |
| } |
| } |
| |
| void C2VDAComponent::onInputBufferDone(int32_t bitstreamId) { |
| DCHECK(mTaskRunner->BelongsToCurrentThread()); |
| ALOGV("onInputBufferDone: bitstream id=%d", bitstreamId); |
| EXPECT_RUNNING_OR_RETURN_ON_ERROR(); |
| |
| C2Work* work = getPendingWorkByBitstreamId(bitstreamId); |
| if (!work) { |
| reportError(C2_CORRUPTED); |
| return; |
| } |
| |
| // When the work is done, the input buffers vector shall be cleared by component. |
| work->input.buffers.clear(); |
| |
| reportFinishedWorkIfAny(); |
| } |
| |
| // This is used as callback while output buffer is released by client. |
| // TODO(johnylin): consider to use C2Buffer::registerOnDestroyNotify instead |
| void C2VDAComponent::returnOutputBuffer(int32_t pictureBufferId) { |
| mTaskRunner->PostTask(FROM_HERE, base::Bind(&C2VDAComponent::onOutputBufferReturned, |
| base::Unretained(this), pictureBufferId)); |
| } |
| |
| void C2VDAComponent::onOutputBufferReturned(int32_t pictureBufferId) { |
| DCHECK(mTaskRunner->BelongsToCurrentThread()); |
| ALOGV("onOutputBufferReturned: picture id=%d", pictureBufferId); |
| EXPECT_RUNNING_OR_RETURN_ON_ERROR(); |
| |
| GraphicBlockInfo* info = getGraphicBlockById(pictureBufferId); |
| if (!info) { |
| reportError(C2_CORRUPTED); |
| return; |
| } |
| CHECK_EQ(info->mState, GraphicBlockInfo::State::OWNED_BY_CLIENT); |
| info->mState = GraphicBlockInfo::State::OWNED_BY_COMPONENT; |
| |
| if (mPendingOutputFormat) { |
| tryChangeOutputFormat(); |
| } else { |
| sendOutputBufferToAccelerator(info); |
| } |
| } |
| |
| void C2VDAComponent::onOutputBufferDone(int32_t pictureBufferId, int32_t bitstreamId) { |
| DCHECK(mTaskRunner->BelongsToCurrentThread()); |
| ALOGV("onOutputBufferDone: picture id=%d, bitstream id=%d", pictureBufferId, bitstreamId); |
| EXPECT_RUNNING_OR_RETURN_ON_ERROR(); |
| |
| C2Work* work = getPendingWorkByBitstreamId(bitstreamId); |
| if (!work) { |
| reportError(C2_CORRUPTED); |
| return; |
| } |
| GraphicBlockInfo* info = getGraphicBlockById(pictureBufferId); |
| if (!info) { |
| reportError(C2_CORRUPTED); |
| return; |
| } |
| CHECK_EQ(info->mState, GraphicBlockInfo::State::OWNED_BY_ACCELERATOR); |
| // Output buffer will be passed to client soon along with mListener->onWorkDone_nb(). |
| info->mState = GraphicBlockInfo::State::OWNED_BY_CLIENT; |
| |
| // Attach output buffer to the work corresponded to bitstreamId. |
| CHECK_EQ(work->worklets.size(), 1u); |
| work->worklets.front()->output.buffers.clear(); |
| work->worklets.front()->output.buffers.emplace_back(std::make_shared<C2VDAGraphicBuffer>( |
| info->mGraphicBlock, base::Bind(&C2VDAComponent::returnOutputBuffer, |
| mWeakThisFactory.GetWeakPtr(), pictureBufferId))); |
| work->worklets.front()->output.ordinal = work->input.ordinal; |
| work->worklets_processed = 1u; |
| |
| int64_t currentTimestamp = base::checked_cast<int64_t>(work->input.ordinal.timestamp); |
| CHECK_GE(currentTimestamp, mLastOutputTimestamp); |
| mLastOutputTimestamp = currentTimestamp; |
| |
| reportFinishedWorkIfAny(); |
| } |
| |
| void C2VDAComponent::onDrain() { |
| DCHECK(mTaskRunner->BelongsToCurrentThread()); |
| ALOGV("onDrain"); |
| EXPECT_STATE_OR_RETURN_ON_ERROR(STARTED); |
| |
| // Set input flag as C2BufferPack::FLAG_END_OF_STREAM to the last queued work. If mQueue is |
| // empty, set to the last work in mPendingWorks and then signal flush immediately. |
| if (!mQueue.empty()) { |
| mQueue.back()->input.flags = static_cast<C2BufferPack::flags_t>( |
| mQueue.back()->input.flags | C2BufferPack::FLAG_END_OF_STREAM); |
| } else if (!mPendingWorks.empty()) { |
| C2Work* work = getPendingWorkLastToFinish(); |
| if (!work) { |
| reportError(C2_CORRUPTED); |
| return; |
| } |
| mPendingWorks.back()->input.flags = static_cast<C2BufferPack::flags_t>( |
| mPendingWorks.back()->input.flags | C2BufferPack::FLAG_END_OF_STREAM); |
| mVDAAdaptor->flush(); |
| mComponentState = ComponentState::DRAINING; |
| } else { |
| // Do nothing. |
| ALOGV("No buffers in VDA, drain takes no effect."); |
| } |
| } |
| |
| void C2VDAComponent::onDrainDone() { |
| DCHECK(mTaskRunner->BelongsToCurrentThread()); |
| ALOGV("onDrainDone"); |
| if (mComponentState == ComponentState::DRAINING) { |
| mComponentState = ComponentState::STARTED; |
| } else if (mComponentState == ComponentState::STOPPING) { |
| // The client signals stop right before VDA notifies drain done. Let stop process goes. |
| return; |
| } else { |
| ALOGE("Unexpected state while onDrainDone(). State=%d", mComponentState); |
| reportError(C2_BAD_STATE); |
| return; |
| } |
| |
| // Last stream is finished. Reset the timestamp record. |
| mLastOutputTimestamp = -1; |
| |
| // Work dequeueing was stopped while component draining. Restart it. |
| mTaskRunner->PostTask(FROM_HERE, |
| base::Bind(&C2VDAComponent::onDequeueWork, base::Unretained(this))); |
| } |
| |
| void C2VDAComponent::onFlush() { |
| DCHECK(mTaskRunner->BelongsToCurrentThread()); |
| ALOGV("onFlush"); |
| EXPECT_STATE_OR_RETURN_ON_ERROR(STARTED); |
| |
| mVDAAdaptor->reset(); |
| // Pop all works in mQueue and put into mPendingWorks. |
| while (!mQueue.empty()) { |
| mPendingWorks.emplace_back(std::move(mQueue.front())); |
| mQueue.pop(); |
| } |
| mComponentState = ComponentState::FLUSHING; |
| } |
| |
| void C2VDAComponent::onStop(base::WaitableEvent* done) { |
| DCHECK(mTaskRunner->BelongsToCurrentThread()); |
| ALOGV("onStop"); |
| EXPECT_RUNNING_OR_RETURN_ON_ERROR(); |
| |
| mVDAAdaptor->reset(); |
| // Pop all works in mQueue and put into mPendingWorks. |
| while (!mQueue.empty()) { |
| mPendingWorks.emplace_back(std::move(mQueue.front())); |
| mQueue.pop(); |
| } |
| |
| mStopDoneEvent = done; // restore done event which shoud be signaled in onStopDone(). |
| mComponentState = ComponentState::STOPPING; |
| } |
| |
| void C2VDAComponent::onResetDone() { |
| DCHECK(mTaskRunner->BelongsToCurrentThread()); |
| if (mComponentState == ComponentState::ERROR) { |
| return; |
| } |
| if (mComponentState == ComponentState::FLUSHING) { |
| onFlushDone(); |
| } else if (mComponentState == ComponentState::STOPPING) { |
| onStopDone(); |
| } else { |
| reportError(C2_CORRUPTED); |
| } |
| } |
| |
| void C2VDAComponent::onFlushDone() { |
| ALOGV("onFlushDone"); |
| reportAbandonedWorks(); |
| // Reset the timestamp record. |
| mLastOutputTimestamp = -1; |
| mComponentState = ComponentState::STARTED; |
| } |
| |
| void C2VDAComponent::onStopDone() { |
| ALOGV("onStopDone"); |
| CHECK(mStopDoneEvent); |
| |
| // Release the graphic block allocator object. |
| mOutputBlockPool.reset(); |
| |
| // TODO(johnylin): At this moment, there may be C2Buffer still owned by client, do we need to |
| // do something for them? |
| reportAbandonedWorks(); |
| mPendingOutputFormat.reset(); |
| mColorFormat = 0u; |
| mLastOutputTimestamp = -1; |
| if (mVDAAdaptor.get()) { |
| mVDAAdaptor->destroy(); |
| mVDAAdaptor.reset(nullptr); |
| } |
| |
| mGraphicBlocks.clear(); |
| |
| mStopDoneEvent->Signal(); |
| mStopDoneEvent = nullptr; |
| mComponentState = ComponentState::UNINITIALIZED; |
| } |
| |
| c2_status_t C2VDAComponent::setListener_vb(const std::shared_ptr<C2Component::Listener>& listener, |
| c2_blocking_t mayBlock) { |
| UNUSED(mayBlock); |
| // TODO(johnylin): API says this method must be supported in all states, however I'm quite not |
| // sure what is the use case. |
| if (mState != State::LOADED) { |
| return C2_BAD_STATE; |
| } |
| mListener = listener; |
| return C2_OK; |
| } |
| |
| void C2VDAComponent::sendInputBufferToAccelerator(const C2ConstLinearBlock& input, |
| int32_t bitstreamId) { |
| ALOGV("sendInputBufferToAccelerator"); |
| int dupFd = dup(input.handle()->data[0]); |
| if (dupFd < 0) { |
| ALOGE("Failed to dup(%d) input buffer (bitstreamId=%d), errno=%d", input.handle()->data[0], |
| bitstreamId, errno); |
| reportError(C2_CORRUPTED); |
| return; |
| } |
| ALOGV("Decode bitstream ID: %d, offset: %u size: %u", bitstreamId, input.offset(), |
| input.size()); |
| mVDAAdaptor->decode(bitstreamId, dupFd, input.offset(), input.size()); |
| } |
| |
| C2Work* C2VDAComponent::getPendingWorkByBitstreamId(int32_t bitstreamId) { |
| auto workIter = std::find_if(mPendingWorks.begin(), mPendingWorks.end(), |
| [bitstreamId](const std::unique_ptr<C2Work>& w) { |
| return frameIndexToBitstreamId(w->input.ordinal.frame_index) == |
| bitstreamId; |
| }); |
| |
| if (workIter == mPendingWorks.end()) { |
| ALOGE("Can't find pending work by bitstream ID: %d", bitstreamId); |
| return nullptr; |
| } |
| return workIter->get(); |
| } |
| |
| C2Work* C2VDAComponent::getPendingWorkLastToFinish() { |
| // Get the work with largest timestamp. |
| auto workIter = std::max_element( |
| mPendingWorks.begin(), mPendingWorks.end(), [](const auto& w1, const auto& w2) { |
| return w1->input.ordinal.timestamp < w2->input.ordinal.timestamp; |
| }); |
| |
| if (workIter == mPendingWorks.end()) { |
| ALOGE("Can't get last finished work from mPendingWork"); |
| return nullptr; |
| } |
| return workIter->get(); |
| } |
| |
| C2VDAComponent::GraphicBlockInfo* C2VDAComponent::getGraphicBlockById(int32_t blockId) { |
| if (blockId < 0 || blockId >= static_cast<int32_t>(mGraphicBlocks.size())) { |
| ALOGE("getGraphicBlockById failed: id=%d", blockId); |
| return nullptr; |
| } |
| return &mGraphicBlocks[blockId]; |
| } |
| |
| void C2VDAComponent::onOutputFormatChanged(std::unique_ptr<VideoFormat> format) { |
| DCHECK(mTaskRunner->BelongsToCurrentThread()); |
| ALOGV("onOutputFormatChanged"); |
| EXPECT_RUNNING_OR_RETURN_ON_ERROR(); |
| |
| ALOGV("New output format(pixel_format=0x%x, min_num_buffers=%u, coded_size=%s, crop_rect=%s)", |
| format->mPixelFormat, format->mMinNumBuffers, format->mCodedSize.ToString().c_str(), |
| format->mVisibleRect.ToString().c_str()); |
| |
| for (auto& info : mGraphicBlocks) { |
| if (info.mState == GraphicBlockInfo::State::OWNED_BY_ACCELERATOR) |
| info.mState = GraphicBlockInfo::State::OWNED_BY_COMPONENT; |
| } |
| |
| CHECK(!mPendingOutputFormat); |
| mPendingOutputFormat = std::move(format); |
| tryChangeOutputFormat(); |
| } |
| |
| void C2VDAComponent::tryChangeOutputFormat() { |
| DCHECK(mTaskRunner->BelongsToCurrentThread()); |
| ALOGV("tryChangeOutputFormat"); |
| CHECK(mPendingOutputFormat); |
| |
| // Change the output format only after all output buffers are returned |
| // from clients. |
| for (const auto& info : mGraphicBlocks) { |
| if (info.mState == GraphicBlockInfo::State::OWNED_BY_CLIENT) { |
| ALOGV("wait buffer: %d for output format change", info.mBlockId); |
| return; |
| } |
| } |
| |
| uint32_t colorFormat; |
| int bufferFormat; |
| switch (mPendingOutputFormat->mPixelFormat) { |
| case HAL_PIXEL_FORMAT_YCbCr_420_888: |
| colorFormat = kColorFormatYUV420Flexible; |
| bufferFormat = HAL_PIXEL_FORMAT_YCbCr_420_888; |
| break; |
| default: |
| ALOGE("pixel format: 0x%x is not supported", mPendingOutputFormat->mPixelFormat); |
| reportError(C2_OMITTED); |
| return; |
| } |
| |
| mOutputFormat.mPixelFormat = mPendingOutputFormat->mPixelFormat; |
| mOutputFormat.mMinNumBuffers = mPendingOutputFormat->mMinNumBuffers; |
| mOutputFormat.mCodedSize = mPendingOutputFormat->mCodedSize; |
| |
| setOutputFormatCrop(mPendingOutputFormat->mVisibleRect); |
| mColorFormat = colorFormat; |
| |
| c2_status_t err = |
| allocateBuffersFromBlockAllocator(mPendingOutputFormat->mCodedSize, bufferFormat); |
| if (err != C2_OK) { |
| reportError(err); |
| return; |
| } |
| |
| for (auto& info : mGraphicBlocks) { |
| sendOutputBufferToAccelerator(&info); |
| } |
| mPendingOutputFormat.reset(); |
| } |
| |
| c2_status_t C2VDAComponent::allocateBuffersFromBlockAllocator(const media::Size& size, |
| int pixelFormat) { |
| ALOGV("allocateBuffersFromBlockAllocator(%s, 0x%x)", size.ToString().c_str(), pixelFormat); |
| |
| size_t bufferCount = mOutputFormat.mMinNumBuffers + kDpbOutputBufferExtraCount; |
| |
| // Allocate the output buffers. |
| mVDAAdaptor->assignPictureBuffers(bufferCount); |
| |
| // TODO: lock access to interface |
| C2BlockPool::local_id_t poolId = mIntf->mOutputBlockPools->flexCount() |
| ? mIntf->mOutputBlockPools->m.mValues[0] |
| : C2BlockPool::BASIC_GRAPHIC; |
| ALOGI("Using C2BlockPool ID = %" PRIu64 " for allocating output buffers", poolId); |
| c2_status_t err; |
| if (!mOutputBlockPool || mOutputBlockPool->getLocalId() != poolId) { |
| err = getCodec2BlockPool(poolId, shared_from_this(), &mOutputBlockPool); |
| if (err != C2_OK) { |
| ALOGE("Graphic block allocator is invalid"); |
| reportError(err); |
| return err; |
| } |
| } |
| |
| mGraphicBlocks.clear(); |
| for (size_t i = 0; i < bufferCount; ++i) { |
| std::shared_ptr<C2GraphicBlock> block; |
| C2MemoryUsage usage = {C2MemoryUsage::kSoftwareRead, 0}; |
| err = mOutputBlockPool->fetchGraphicBlock(size.width(), size.height(), pixelFormat, usage, |
| &block); |
| if (err != C2_OK) { |
| mGraphicBlocks.clear(); |
| ALOGE("failed to allocate buffer: %d", err); |
| reportError(err); |
| return err; |
| } |
| appendOutputBuffer(std::move(block)); |
| } |
| mOutputFormat.mMinNumBuffers = bufferCount; |
| return C2_OK; |
| } |
| |
| void C2VDAComponent::appendOutputBuffer(std::shared_ptr<C2GraphicBlock> block) { |
| GraphicBlockInfo info; |
| info.mBlockId = mGraphicBlocks.size(); |
| info.mGraphicBlock = std::move(block); |
| |
| const C2GraphicView& view = info.mGraphicBlock->map().get(); |
| const uint8_t* const* data = view.data(); |
| CHECK_NE(data, nullptr); |
| const C2PlaneLayout& layout = view.layout(); |
| |
| ALOGV("allocate graphic buffer: %p, id: %u, size: %dx%d", info.mGraphicBlock->handle(), |
| info.mBlockId, info.mGraphicBlock->width(), info.mGraphicBlock->height()); |
| |
| // get offset from data pointers |
| uint32_t offsets[C2PlaneLayout::MAX_NUM_PLANES]; |
| auto baseAddress = reinterpret_cast<intptr_t>(data[0]); |
| for (uint32_t i = 0; i < layout.mNumPlanes; ++i) { |
| auto planeAddress = reinterpret_cast<intptr_t>(data[i]); |
| offsets[i] = static_cast<uint32_t>(planeAddress - baseAddress); |
| } |
| |
| for (uint32_t i = 0; i < layout.mNumPlanes; ++i) { |
| ALOGV("plane %u: stride: %d, offset: %u", i, layout.mPlanes[i].mRowInc, offsets[i]); |
| } |
| |
| base::ScopedFD passedHandle(dup(info.mGraphicBlock->handle()->data[0])); |
| if (!passedHandle.is_valid()) { |
| ALOGE("Failed to dup(%d), errno=%d", info.mGraphicBlock->handle()->data[0], errno); |
| reportError(C2_CORRUPTED); |
| return; |
| } |
| std::vector<VideoFramePlane> passedPlanes; |
| for (uint32_t i = 0; i < layout.mNumPlanes; ++i) { |
| CHECK_GT(layout.mPlanes[i].mRowInc, 0); |
| passedPlanes.push_back({offsets[i], static_cast<uint32_t>(layout.mPlanes[i].mRowInc)}); |
| } |
| info.mHandle = std::move(passedHandle); |
| info.mPlanes = std::move(passedPlanes); |
| |
| mGraphicBlocks.push_back(std::move(info)); |
| } |
| |
| void C2VDAComponent::sendOutputBufferToAccelerator(GraphicBlockInfo* info) { |
| ALOGV("sendOutputBufferToAccelerator index=%d", info->mBlockId); |
| CHECK_EQ(info->mState, GraphicBlockInfo::State::OWNED_BY_COMPONENT); |
| info->mState = GraphicBlockInfo::State::OWNED_BY_ACCELERATOR; |
| |
| // is_valid() is true for the first time the buffer is passed to VDA. In that case, VDA needs to |
| // import the buffer first. |
| if (info->mHandle.is_valid()) { |
| mVDAAdaptor->importBufferForPicture(info->mBlockId, info->mHandle.release(), info->mPlanes); |
| } else { |
| mVDAAdaptor->reusePictureBuffer(info->mBlockId); |
| } |
| } |
| |
| void C2VDAComponent::onVisibleRectChanged(const media::Rect& cropRect) { |
| DCHECK(mTaskRunner->BelongsToCurrentThread()); |
| ALOGV("onVisibleRectChanged"); |
| EXPECT_RUNNING_OR_RETURN_ON_ERROR(); |
| |
| // We should make sure there is no pending output format change. That is, the input cropRect is |
| // corresponding to current output format. |
| CHECK(mPendingOutputFormat == nullptr); |
| setOutputFormatCrop(cropRect); |
| } |
| |
| void C2VDAComponent::setOutputFormatCrop(const media::Rect& cropRect) { |
| ALOGV("setOutputFormatCrop(%dx%d)", cropRect.width(), cropRect.height()); |
| mOutputFormat.mVisibleRect = cropRect; |
| // TODO(johnylin): what else do we need to do? crop rect could be an info requested from |
| // framework by requestedInfos in worklets. |
| } |
| |
| c2_status_t C2VDAComponent::queue_nb(std::list<std::unique_ptr<C2Work>>* const items) { |
| if (mState != State::RUNNING) { |
| return C2_BAD_STATE; |
| } |
| while (!items->empty()) { |
| mTaskRunner->PostTask(FROM_HERE, |
| base::Bind(&C2VDAComponent::onQueueWork, base::Unretained(this), |
| base::Passed(&items->front()))); |
| items->pop_front(); |
| } |
| return C2_OK; |
| } |
| |
| c2_status_t C2VDAComponent::announce_nb(const std::vector<C2WorkOutline>& items) { |
| UNUSED(items); |
| return C2_OMITTED; // Tunneling is not supported by now |
| } |
| |
| c2_status_t C2VDAComponent::flush_sm(flush_mode_t mode, |
| std::list<std::unique_ptr<C2Work>>* const flushedWork) { |
| if (mode != FLUSH_COMPONENT) { |
| return C2_OMITTED; // Tunneling is not supported by now |
| } |
| if (mState != State::RUNNING) { |
| return C2_BAD_STATE; |
| } |
| mTaskRunner->PostTask(FROM_HERE, base::Bind(&C2VDAComponent::onFlush, base::Unretained(this))); |
| // Instead of |flushedWork|, abandoned works will be returned via onWorkDone_nb() callback. |
| return C2_OK; |
| } |
| |
| c2_status_t C2VDAComponent::drain_nb(drain_mode_t mode) { |
| if (mode != DRAIN_COMPONENT_WITH_EOS) { |
| return C2_OMITTED; // Tunneling is not supported by now |
| } |
| if (mState != State::RUNNING) { |
| return C2_BAD_STATE; |
| } |
| mTaskRunner->PostTask(FROM_HERE, base::Bind(&C2VDAComponent::onDrain, base::Unretained(this))); |
| return C2_OK; |
| } |
| |
| c2_status_t C2VDAComponent::start() { |
| if (mState != State::LOADED) { |
| return C2_BAD_STATE; // start() is only supported when component is in LOADED state. |
| } |
| |
| fetchParametersFromIntf(); |
| base::WaitableEvent done(base::WaitableEvent::ResetPolicy::AUTOMATIC, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| mTaskRunner->PostTask(FROM_HERE, base::Bind(&C2VDAComponent::onStart, base::Unretained(this), |
| mCodecProfile, &done)); |
| done.Wait(); |
| if (mVDAInitResult != VideoDecodeAcceleratorAdaptor::Result::SUCCESS) { |
| ALOGE("Failed to start component due to VDA error: %d", static_cast<int>(mVDAInitResult)); |
| return C2_CORRUPTED; |
| } |
| mState = State::RUNNING; |
| return C2_OK; |
| } |
| |
| c2_status_t C2VDAComponent::stop() { |
| if (!(mState == State::RUNNING || mState == State::ERROR)) { |
| return C2_BAD_STATE; // component is already in stopped state. |
| } |
| |
| base::WaitableEvent done(base::WaitableEvent::ResetPolicy::AUTOMATIC, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| mTaskRunner->PostTask(FROM_HERE, |
| base::Bind(&C2VDAComponent::onStop, base::Unretained(this), &done)); |
| done.Wait(); |
| mState = State::LOADED; |
| return C2_OK; |
| } |
| |
| c2_status_t C2VDAComponent::reset() { |
| return stop(); |
| // TODO(johnylin): reset is different than stop that it could be called in any state. |
| // TODO(johnylin): when reset is called, set ComponentInterface to default values. |
| } |
| |
| c2_status_t C2VDAComponent::release() { |
| // TODO(johnylin): what should we do for release? |
| return C2_OMITTED; |
| } |
| |
| std::shared_ptr<C2ComponentInterface> C2VDAComponent::intf() { |
| return mIntf; |
| } |
| |
| void C2VDAComponent::providePictureBuffers(uint32_t pixelFormat, uint32_t minNumBuffers, |
| const media::Size& codedSize) { |
| // Uses coded size for crop rect while it is not available. |
| auto format = std::make_unique<VideoFormat>(pixelFormat, minNumBuffers, codedSize, |
| media::Rect(codedSize)); |
| |
| // Set mRequestedVisibleRect to default. |
| mRequestedVisibleRect = media::Rect(); |
| |
| mTaskRunner->PostTask(FROM_HERE, base::Bind(&C2VDAComponent::onOutputFormatChanged, |
| base::Unretained(this), base::Passed(&format))); |
| } |
| |
| void C2VDAComponent::dismissPictureBuffer(int32_t pictureBufferId) { |
| UNUSED(pictureBufferId); |
| // no ops |
| } |
| |
| void C2VDAComponent::pictureReady(int32_t pictureBufferId, int32_t bitstreamId, |
| const media::Rect& cropRect) { |
| UNUSED(pictureBufferId); |
| UNUSED(bitstreamId); |
| |
| if (mRequestedVisibleRect != cropRect) { |
| mRequestedVisibleRect = cropRect; |
| mTaskRunner->PostTask(FROM_HERE, base::Bind(&C2VDAComponent::onVisibleRectChanged, |
| base::Unretained(this), cropRect)); |
| } |
| |
| mTaskRunner->PostTask(FROM_HERE, |
| base::Bind(&C2VDAComponent::onOutputBufferDone, base::Unretained(this), |
| pictureBufferId, bitstreamId)); |
| } |
| |
| void C2VDAComponent::notifyEndOfBitstreamBuffer(int32_t bitstreamId) { |
| mTaskRunner->PostTask(FROM_HERE, base::Bind(&C2VDAComponent::onInputBufferDone, |
| base::Unretained(this), bitstreamId)); |
| } |
| |
| void C2VDAComponent::notifyFlushDone() { |
| mTaskRunner->PostTask(FROM_HERE, |
| base::Bind(&C2VDAComponent::onDrainDone, base::Unretained(this))); |
| } |
| |
| void C2VDAComponent::notifyResetDone() { |
| mTaskRunner->PostTask(FROM_HERE, |
| base::Bind(&C2VDAComponent::onResetDone, base::Unretained(this))); |
| } |
| |
| void C2VDAComponent::notifyError(VideoDecodeAcceleratorAdaptor::Result error) { |
| ALOGE("Got notifyError from VDA error=%d", error); |
| c2_status_t err; |
| switch (error) { |
| case VideoDecodeAcceleratorAdaptor::Result::ILLEGAL_STATE: |
| err = C2_BAD_STATE; |
| break; |
| case VideoDecodeAcceleratorAdaptor::Result::INVALID_ARGUMENT: |
| case VideoDecodeAcceleratorAdaptor::Result::UNREADABLE_INPUT: |
| err = C2_BAD_VALUE; |
| break; |
| case VideoDecodeAcceleratorAdaptor::Result::PLATFORM_FAILURE: |
| err = C2_CORRUPTED; |
| break; |
| case VideoDecodeAcceleratorAdaptor::Result::INSUFFICIENT_RESOURCES: |
| err = C2_NO_MEMORY; |
| break; |
| case VideoDecodeAcceleratorAdaptor::Result::SUCCESS: |
| ALOGE("Shouldn't get SUCCESS err code in NotifyError(). Skip it..."); |
| return; |
| } |
| reportError(err); |
| } |
| |
| void C2VDAComponent::reportFinishedWorkIfAny() { |
| DCHECK(mTaskRunner->BelongsToCurrentThread()); |
| std::vector<std::unique_ptr<C2Work>> finishedWorks; |
| |
| // Work should be reported as done if both input and output buffer are returned by VDA. |
| |
| // Note that not every input buffer has matched output (ex. CSD header for H.264). |
| // However, the timestamp is guaranteed to be monotonic increasing for buffers in display order. |
| // That is, since VDA output is in display order, if we get a returned output with timestamp T, |
| // it implies all works with timestamp <= T are done. |
| auto iter = mPendingWorks.begin(); |
| while (iter != mPendingWorks.end()) { |
| if (isWorkDone(iter->get())) { |
| iter->get()->result = C2_OK; |
| finishedWorks.emplace_back(std::move(*iter)); |
| iter = mPendingWorks.erase(iter); |
| } else { |
| ++iter; |
| } |
| } |
| |
| if (!finishedWorks.empty()) { |
| mListener->onWorkDone_nb(shared_from_this(), std::move(finishedWorks)); |
| } |
| } |
| |
| bool C2VDAComponent::isWorkDone(const C2Work* work) const { |
| if (!work->input.buffers.empty()) { |
| return false; // Input buffer is still owned by VDA. |
| } |
| if (mLastOutputTimestamp < 0) { |
| return false; // No output buffer is returned yet. |
| } |
| if (work->input.ordinal.timestamp > static_cast<uint64_t>(mLastOutputTimestamp)) { |
| return false; // Output buffer is not returned by VDA yet. |
| } |
| return true; // Output buffer is returned, or it has no related output buffer. |
| } |
| |
| void C2VDAComponent::reportAbandonedWorks() { |
| DCHECK(mTaskRunner->BelongsToCurrentThread()); |
| std::vector<std::unique_ptr<C2Work>> abandonedWorks; |
| |
| while (!mPendingWorks.empty()) { |
| std::unique_ptr<C2Work> work(std::move(mPendingWorks.front())); |
| mPendingWorks.pop_front(); |
| |
| work->result = static_cast<c2_status_t>(-1); // What should this value be? |
| // When the work is abandoned, the input buffers vector shall be cleared by component. |
| work->input.buffers.clear(); |
| abandonedWorks.emplace_back(std::move(work)); |
| } |
| |
| if (!abandonedWorks.empty()) { |
| mListener->onWorkDone_nb(shared_from_this(), std::move(abandonedWorks)); |
| } |
| } |
| |
| void C2VDAComponent::reportError(c2_status_t error) { |
| uint32_t reported_error = -error; |
| // Why onError_nb takes uint32_t while c2_status_t is mostly negative numbers? |
| mListener->onError_nb(shared_from_this(), reported_error); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Neglect flexible flag while matching parameter indices. |
| #define CASE(paramType) \ |
| case paramType::coreIndex: \ |
| return std::unique_ptr<C2StructDescriptor>(new C2StructDescriptor{ \ |
| paramType::coreIndex, \ |
| paramType::fieldList, \ |
| }) |
| |
| class C2VDAComponentStore::ParamReflector : public C2ParamReflector { |
| public: |
| virtual std::unique_ptr<C2StructDescriptor> describe(C2Param::BaseIndex coreIndex) override { |
| switch (coreIndex.coreIndex()) { |
| //CASE(C2ComponentDomainInfo); //TODO: known codec2 framework bug |
| CASE(C2StreamFormatConfig); |
| CASE(C2VideoSizeStreamInfo); |
| CASE(C2PortMimeConfig); |
| CASE(C2MaxVideoSizeHintPortSetting); |
| } |
| return nullptr; |
| } |
| }; |
| |
| #undef CASE |
| |
| // TODO(johnylin): implement C2VDAComponentStore |
| C2VDAComponentStore::C2VDAComponentStore() : mParamReflector(std::make_shared<ParamReflector>()) {} |
| |
| C2String C2VDAComponentStore::getName() const { |
| return "android.componentStore.v4l2"; |
| } |
| |
| c2_status_t C2VDAComponentStore::createComponent(C2String name, |
| std::shared_ptr<C2Component>* const component) { |
| UNUSED(name); |
| UNUSED(component); |
| return C2_OMITTED; |
| } |
| |
| c2_status_t C2VDAComponentStore::createInterface( |
| C2String name, std::shared_ptr<C2ComponentInterface>* const interface) { |
| interface->reset(new C2VDAComponentIntf(name, 12345)); |
| return C2_OK; |
| } |
| |
| std::vector<std::shared_ptr<const C2Component::Traits>> C2VDAComponentStore::listComponents() { |
| return std::vector<std::shared_ptr<const C2Component::Traits>>(); |
| } |
| |
| c2_status_t C2VDAComponentStore::copyBuffer(std::shared_ptr<C2GraphicBuffer> src, |
| std::shared_ptr<C2GraphicBuffer> dst) { |
| UNUSED(src); |
| UNUSED(dst); |
| return C2_OMITTED; |
| } |
| |
| std::shared_ptr<C2ParamReflector> C2VDAComponentStore::getParamReflector() const { |
| return mParamReflector; |
| } |
| |
| c2_status_t C2VDAComponentStore::querySupportedParams_nb( |
| std::vector<std::shared_ptr<C2ParamDescriptor>>* const params) const { |
| UNUSED(params); |
| return C2_OMITTED; |
| } |
| |
| c2_status_t C2VDAComponentStore::querySupportedValues_sm( |
| std::vector<C2FieldSupportedValuesQuery>& fields) const { |
| UNUSED(fields); |
| return C2_OMITTED; |
| } |
| |
| c2_status_t C2VDAComponentStore::query_sm( |
| const std::vector<C2Param* const>& stackParams, |
| const std::vector<C2Param::Index>& heapParamIndices, |
| std::vector<std::unique_ptr<C2Param>>* const heapParams) const { |
| UNUSED(stackParams); |
| UNUSED(heapParamIndices); |
| UNUSED(heapParams); |
| return C2_OMITTED; |
| } |
| |
| c2_status_t C2VDAComponentStore::config_sm( |
| const std::vector<C2Param* const>& params, |
| std::vector<std::unique_ptr<C2SettingResult>>* const failures) { |
| UNUSED(params); |
| UNUSED(failures); |
| return C2_OMITTED; |
| } |
| |
| } // namespace android |
| |
| // ---------------------- Factory Functions Interface ---------------- |
| |
| using namespace android; |
| |
| extern "C" C2ComponentStore* create_store() { |
| return new C2VDAComponentStore(); |
| } |
| |
| extern "C" void destroy_store(C2ComponentStore* store) { |
| delete store; |
| } |