/* * Copyright (C) 2023 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. */ #include #include #include #include #define LOG_TAG "EffectProxy" // #define LOG_NDEBUG 0 #include #include #include #include "EffectProxy.h" using ::aidl::android::hardware::audio::effect::Capability; using ::aidl::android::hardware::audio::effect::CommandId; using ::aidl::android::hardware::audio::effect::Descriptor; using ::aidl::android::hardware::audio::effect::Flags; using ::aidl::android::hardware::audio::effect::IEffect; using ::aidl::android::hardware::audio::effect::IFactory; using ::aidl::android::hardware::audio::effect::Parameter; using ::aidl::android::hardware::audio::effect::State; using ::aidl::android::media::audio::common::AudioUuid; namespace android::effect { EffectProxy::EffectProxy(const AudioUuid& uuid, const std::vector& descriptors, const std::shared_ptr& factory) : mDescriptorCommon(buildDescriptorCommon(uuid, descriptors)), mSubEffects( [](const std::vector& descs, const std::shared_ptr& factory) { std::vector subEffects; ALOG_ASSERT(factory, "invalid EffectFactory handle"); ndk::ScopedAStatus status = ndk::ScopedAStatus::ok(); for (const auto& desc : descs) { SubEffect sub({.descriptor = desc}); status = factory->createEffect(desc.common.id.uuid, &sub.handle); if (!status.isOk() || !sub.handle) { sub.handle = nullptr; ALOGW("%s create sub-effect %s failed", __func__, ::android::audio::utils::toString(desc.common.id.uuid).c_str()); } subEffects.emplace_back(sub); } return subEffects; }(descriptors, factory)), mFactory(factory) {} EffectProxy::~EffectProxy() { close(); destroy(); mSubEffects.clear(); } ndk::ScopedAStatus EffectProxy::destroy() { ALOGV("%s: %s", __func__, ::android::audio::utils::toString(mDescriptorCommon.id.type).c_str()); return runWithAllSubEffects([&](std::shared_ptr& effect) { ndk::ScopedAStatus status = mFactory->destroyEffect(effect); if (status.isOk()) { effect.reset(); } return status; }); } ndk::ScopedAStatus EffectProxy::setOffloadParam(const effect_offload_param_t* offload) { const auto& itor = std::find_if(mSubEffects.begin(), mSubEffects.end(), [&](const auto& sub) { const auto& desc = sub.descriptor; return offload->isOffload == desc.common.flags.offloadIndication; }); if (itor == mSubEffects.end()) { ALOGE("%s no %soffload sub-effect found", __func__, offload->isOffload ? "" : "non-"); mActiveSubIdx = 0; return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_NULL_POINTER, "noActiveEffctFound"); } mActiveSubIdx = std::distance(mSubEffects.begin(), itor); ALOGI("%s: active %soffload sub-effect %zu: %s", __func__, offload->isOffload ? "" : "non-", mActiveSubIdx, ::android::audio::utils::toString(mSubEffects[mActiveSubIdx].descriptor.common.id.uuid) .c_str()); return runWithAllSubEffects([&](std::shared_ptr& effect) { return effect->setParameter(Parameter::make(offload->isOffload)); }); } // EffectProxy go over sub-effects and call IEffect interfaces ndk::ScopedAStatus EffectProxy::open(const Parameter::Common& common, const std::optional& specific, IEffect::OpenEffectReturn* ret __unused) { ndk::ScopedAStatus status = ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_STATE, "nullEffectHandle"); for (auto& sub : mSubEffects) { IEffect::OpenEffectReturn openReturn; if (!sub.handle || !(status = sub.handle->open(common, specific, &openReturn)).isOk()) { ALOGE("%s: failed to open %p UUID %s", __func__, sub.handle.get(), ::android::audio::utils::toString(sub.descriptor.common.id.uuid).c_str()); break; } sub.effectMq.statusQ = std::make_shared(openReturn.statusMQ); sub.effectMq.inputQ = std::make_shared(openReturn.inputDataMQ); sub.effectMq.outputQ = std::make_shared(openReturn.outputDataMQ); } // close all opened effects if failure if (!status.isOk()) { ALOGE("%s: closing all sub-effects with error %s", __func__, status.getDescription().c_str()); close(); } return status; } ndk::ScopedAStatus EffectProxy::reopen(OpenEffectReturn* ret __unused) { ndk::ScopedAStatus status = ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_STATE, "nullEffectHandle"); for (auto& sub : mSubEffects) { IEffect::OpenEffectReturn openReturn; if (!sub.handle || !(status = sub.handle->reopen(&openReturn)).isOk()) { ALOGE("%s: failed to open %p UUID %s", __func__, sub.handle.get(), ::android::audio::utils::toString(sub.descriptor.common.id.uuid).c_str()); break; } sub.effectMq.statusQ = std::make_shared(openReturn.statusMQ); sub.effectMq.inputQ = std::make_shared(openReturn.inputDataMQ); sub.effectMq.outputQ = std::make_shared(openReturn.outputDataMQ); } // close all opened effects if failure if (!status.isOk()) { ALOGW("%s: closing all sub-effects with error %s", __func__, status.getDescription().c_str()); close(); } return status; } ndk::ScopedAStatus EffectProxy::close() { command(CommandId::STOP); return runWithAllSubEffects([&](std::shared_ptr& effect) { return effect->close(); }); } ndk::ScopedAStatus EffectProxy::getDescriptor(Descriptor* desc) { *desc = mSubEffects[mActiveSubIdx].descriptor; desc->common = mDescriptorCommon; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus EffectProxy::buildDescriptor(const AudioUuid& uuid, const std::vector& subEffectDescs, Descriptor* desc) { if (!desc) { ALOGE("%s: null descriptor pointer", __func__); return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_NULL_POINTER, "nullptr"); } if (subEffectDescs.size() < 2) { ALOGE("%s: proxy need at least 2 sub-effects, got %zu", __func__, subEffectDescs.size()); return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT, "needMoreSubEffects"); } desc->common = buildDescriptorCommon(uuid, subEffectDescs); return ndk::ScopedAStatus::ok(); } // Sub-effects are required to have identical features, so here we return the SW sub-effect // descriptor, with the implementation UUID replaced with proxy UUID, and flags setting respect all // sub-effects. Descriptor::Common EffectProxy::buildDescriptorCommon( const AudioUuid& uuid, const std::vector& subEffectDescs) { Descriptor::Common swCommon; const Flags& firstFlag = subEffectDescs[0].common.flags; bool offloadExist = false; for (const auto& desc : subEffectDescs) { if (desc.common.flags.offloadIndication) { offloadExist = true; } else { swCommon = desc.common; } if (desc.common.flags.audioModeIndication != firstFlag.audioModeIndication || desc.common.flags.audioSourceIndication != firstFlag.audioSourceIndication || desc.common.flags.sinkMetadataIndication != firstFlag.sinkMetadataIndication || desc.common.flags.sourceMetadataIndication != firstFlag.sourceMetadataIndication || desc.common.flags.deviceIndication != firstFlag.deviceIndication) { ALOGW("Inconsistent flags %s vs %s", desc.common.flags.toString().c_str(), firstFlag.toString().c_str()); } } swCommon.flags.offloadIndication = offloadExist; // replace implementation UUID with proxy UUID. swCommon.id.uuid = uuid; swCommon.id.proxy = std::nullopt; return swCommon; } // Handle with active sub-effect first, only send to other sub-effects when success ndk::ScopedAStatus EffectProxy::command(CommandId id) { return runWithActiveSubEffectThenOthers( [&](const std::shared_ptr& effect) -> ndk::ScopedAStatus { return effect->command(id); }); } // Return the active sub-effect state ndk::ScopedAStatus EffectProxy::getState(State* state) { return runWithActiveSubEffect( [&](const std::shared_ptr& effect) -> ndk::ScopedAStatus { return effect->getState(state); }); } // Handle with active sub-effect first, only send to other sub-effects when success ndk::ScopedAStatus EffectProxy::setParameter(const Parameter& param) { return runWithActiveSubEffectThenOthers( [&](const std::shared_ptr& effect) -> ndk::ScopedAStatus { return effect->setParameter(param); }); } // Return the active sub-effect parameter ndk::ScopedAStatus EffectProxy::getParameter(const Parameter::Id& id, Parameter* param) { return runWithActiveSubEffect( [&](const std::shared_ptr& effect) -> ndk::ScopedAStatus { return effect->getParameter(id, param); }); } ndk::ScopedAStatus EffectProxy::runWithActiveSubEffectThenOthers( std::function&)> const& func) { ndk::ScopedAStatus status = runWithActiveSubEffect(func); if (!status.isOk()) { ALOGW("%s active sub-effect return error %s", __func__, status.getDescription().c_str()); } // proceed with others for (size_t i = 0; i < mSubEffects.size(); i++) { if (i == mActiveSubIdx) { continue; } if (!mSubEffects[i].handle) { ALOGW("%s null sub-effect interface for %s", __func__, mSubEffects[i].descriptor.common.id.uuid.toString().c_str()); continue; } func(mSubEffects[i].handle); } return status; } ndk::ScopedAStatus EffectProxy::runWithActiveSubEffect( std::function&)> const& func) { if (!mSubEffects[mActiveSubIdx].handle) { ALOGE("%s null active sub-effect interface, active %s", __func__, mSubEffects[mActiveSubIdx].descriptor.toString().c_str()); return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_NULL_POINTER, "activeSubEffectNull"); } return func(mSubEffects[mActiveSubIdx].handle); } ndk::ScopedAStatus EffectProxy::runWithAllSubEffects( std::function&)> const& func) { ndk::ScopedAStatus status = ndk::ScopedAStatus::ok(); // proceed with others if active sub-effect success for (auto& sub : mSubEffects) { if (!sub.handle) { ALOGW("%s null sub-effect interface %s", __func__, sub.descriptor.toString().c_str()); continue; } ndk::ScopedAStatus temp = func(sub.handle); if (!temp.isOk()) { status = ndk::ScopedAStatus::fromStatus(temp.getStatus()); } } return status; } bool EffectProxy::isBypassing() const { return mSubEffects[mActiveSubIdx].descriptor.common.flags.bypass; } bool EffectProxy::isTunnel() const { return mSubEffects[mActiveSubIdx].descriptor.common.flags.hwAcceleratorMode == Flags::HardwareAccelerator::TUNNEL; } binder_status_t EffectProxy::dump(int fd, const char** args, uint32_t numArgs) { const std::string dumpString = toString(); write(fd, dumpString.c_str(), dumpString.size()); return runWithAllSubEffects([&](std::shared_ptr& effect) { return ndk::ScopedAStatus::fromStatus(effect->dump(fd, args, numArgs)); }) .getStatus(); } std::string EffectProxy::toString(size_t level) const { std::string prefixSpace(level, ' '); std::string ss = prefixSpace + "EffectProxy:\n"; prefixSpace += " "; base::StringAppendF(&ss, "%sDescriptorCommon: %s\n", prefixSpace.c_str(), mDescriptorCommon.toString().c_str()); base::StringAppendF(&ss, "%sActiveSubIdx: %zu\n", prefixSpace.c_str(), mActiveSubIdx); base::StringAppendF(&ss, "%sAllSubEffects:\n", prefixSpace.c_str()); for (size_t i = 0; i < mSubEffects.size(); i++) { base::StringAppendF(&ss, "%s[%zu] - Handle: %p, %s\n", prefixSpace.c_str(), i, mSubEffects[i].handle.get(), mSubEffects[i].descriptor.toString().c_str()); } return ss; } } // namespace android::effect