Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e8eff23f authored by Shunkai Yao's avatar Shunkai Yao
Browse files

Refine EffectProxy logic

To allow create multiple EffectProxy instances for same type
Implement dump with all sub-effects

Bug: 271500140
Test: Enable AIDL and flash to pixel
Test: Play Youtube music with effect on/off
Test: dumpsys media.audio_flinger

Change-Id: I468d7e8712d7b098d869f147a4b40881ef11cabb
Merged-In: I468d7e8712d7b098d869f147a4b40881ef11cabb
parent dce4376f
Loading
Loading
Loading
Loading
+25 −14
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@
 * limitations under the License.
 */

#include <csignal>
#include <cstddef>
#include <cstdint>
#include <cstring>
@@ -194,11 +195,9 @@ status_t EffectConversionHelperAidl::handleSetConfig(uint32_t cmdSize, const voi
                statusTFromBinderStatus(mEffect->open(common, std::nullopt, &openReturn)));

        if (mIsProxyEffect) {
            const auto& ret =
                    std::static_pointer_cast<EffectProxy>(mEffect)->getEffectReturnParam();
            mStatusQ = std::make_shared<StatusMQ>(ret->statusMQ);
            mInputQ = std::make_shared<DataMQ>(ret->inputDataMQ);
            mOutputQ = std::make_shared<DataMQ>(ret->outputDataMQ);
            mStatusQ = std::static_pointer_cast<EffectProxy>(mEffect)->getStatusMQ();
            mInputQ = std::static_pointer_cast<EffectProxy>(mEffect)->getInputMQ();
            mOutputQ = std::static_pointer_cast<EffectProxy>(mEffect)->getOutputMQ();
        } else {
            mStatusQ = std::make_shared<StatusMQ>(openReturn.statusMQ);
            mInputQ = std::make_shared<DataMQ>(openReturn.inputDataMQ);
@@ -206,6 +205,7 @@ status_t EffectConversionHelperAidl::handleSetConfig(uint32_t cmdSize, const voi
        }

        if (status_t status = updateEventFlags(); status != OK) {
            ALOGV("%s closing at status %d", __func__, status);
            mEffect->close();
            return status;
        }
@@ -353,14 +353,15 @@ status_t EffectConversionHelperAidl::handleSetOffload(uint32_t cmdSize, const vo
        ALOGI("%s offload param offload %s ioHandle %d", __func__,
              offload->isOffload ? "true" : "false", offload->ioHandle);
        mCommon.ioHandle = offload->ioHandle;
        RETURN_STATUS_IF_ERROR(statusTFromBinderStatus(
                std::static_pointer_cast<EffectProxy>(mEffect)->setOffloadParam(offload)));
        // update FMQs
        const auto& ret = std::static_pointer_cast<EffectProxy>(mEffect)->getEffectReturnParam();
        mStatusQ = std::make_shared<StatusMQ>(ret->statusMQ);
        mInputQ = std::make_shared<DataMQ>(ret->inputDataMQ);
        mOutputQ = std::make_shared<DataMQ>(ret->outputDataMQ);
        RETURN_STATUS_IF_ERROR(updateEventFlags());
        const auto& effectProxy = std::static_pointer_cast<EffectProxy>(mEffect);
        RETURN_STATUS_IF_ERROR(statusTFromBinderStatus(effectProxy->setOffloadParam(offload)));
        // update FMQs if the effect instance already open
        if (State state; effectProxy->getState(&state).isOk() && state != State::INIT) {
            mStatusQ = effectProxy->getStatusMQ();
            mInputQ = effectProxy->getInputMQ();
            mOutputQ = effectProxy->getOutputMQ();
            updateEventFlags();
        }
    }
    return *static_cast<int32_t*>(pReplyData) = OK;
}
@@ -408,17 +409,27 @@ status_t EffectConversionHelperAidl::handleVisualizerMeasure(uint32_t cmdSize __
status_t EffectConversionHelperAidl::updateEventFlags() {
    status_t status = BAD_VALUE;
    EventFlag* efGroup = nullptr;
    if (mStatusQ->isValid()) {
    if (mStatusQ && mStatusQ->isValid()) {
        status = EventFlag::createEventFlag(mStatusQ->getEventFlagWord(), &efGroup);
        if (status != OK || !efGroup) {
            ALOGE("%s: create EventFlagGroup failed, ret %d, egGroup %p", __func__, status,
                  efGroup);
            status = (status == OK) ? BAD_VALUE : status;
        }
    } else if (isBypassing()) {
        // for effect with bypass (no processing) flag, it's okay to not have statusQ
        return OK;
    }

    mEfGroup.reset(efGroup, EventFlagDeleter());
    return status;
}

bool EffectConversionHelperAidl::isBypassing() const {
    return mEffect &&
           (mDesc.common.flags.bypass ||
            (mIsProxyEffect && std::static_pointer_cast<EffectProxy>(mEffect)->isBypassing()));
}

}  // namespace effect
}  // namespace android
+1 −0
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ class EffectConversionHelperAidl {
    std::shared_ptr<DataMQ> getInputMQ() { return mInputQ; }
    std::shared_ptr<DataMQ> getOutputMQ() { return mOutputQ; }
    std::shared_ptr<android::hardware::EventFlag> getEventFlagGroup() { return mEfGroup; }
    bool isBypassing() const;

  protected:
    const int32_t mSessionId;
+9 −3
Original line number Diff line number Diff line
@@ -74,9 +74,12 @@ EffectHalAidl::EffectHalAidl(const std::shared_ptr<IFactory>& factory,
}

EffectHalAidl::~EffectHalAidl() {
    if (mEffect) {
        mIsProxyEffect ? std::static_pointer_cast<EffectProxy>(mEffect)->destroy()
                       : mFactory->destroyEffect(mEffect);
    if (mFactory && mEffect) {
        if (mIsProxyEffect) {
            std::static_pointer_cast<EffectProxy>(mEffect)->destroy();
        } else {
            mFactory->destroyEffect(mEffect);
        }
    }
}

@@ -166,6 +169,9 @@ status_t EffectHalAidl::process() {
    auto inputQ = mConversion->getInputMQ();
    auto outputQ = mConversion->getOutputMQ();
    auto efGroup = mConversion->getEventFlagGroup();
    if (mConversion->isBypassing()) {
        return OK;
    }
    if (!statusQ || !statusQ->isValid() || !inputQ || !inputQ->isValid() || !outputQ ||
        !outputQ->isValid() || !efGroup) {
        ALOGE("%s invalid FMQ [Status %d I %d O %d] efGroup %p", __func__,
+133 −127
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */

#include <algorithm>
#include <iterator>
#include <memory>
#define LOG_TAG "EffectProxy"
// #define LOG_NDEBUG 0
@@ -25,6 +26,7 @@

#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;
@@ -33,89 +35,39 @@ 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;
using ::android::audio::utils::toString;

namespace android {
namespace effect {

EffectProxy::EffectProxy(const Descriptor::Identity& id, const std::shared_ptr<IFactory>& factory)
    : mIdentity([](const Descriptor::Identity& subId) {
          // update EffectProxy implementation UUID to the sub-effect proxy UUID
          ALOG_ASSERT(subId.proxy.has_value(), "Sub-effect Identity must have valid proxy UUID");
          Descriptor::Identity tempId = subId;
          tempId.uuid = subId.proxy.value();
          return tempId;
      }(id)),
      mFactory(factory) {}

EffectProxy::~EffectProxy() {
    close();
    destroy();
    mSubEffects.clear();
}

// sub effect must have same proxy UUID as EffectProxy, and the type UUID must match.
ndk::ScopedAStatus EffectProxy::addSubEffect(const Descriptor& sub) {
    ALOGV("%s: %s", __func__, toString(mIdentity.type).c_str());
    if (0 != mSubEffects.count(sub.common.id) || !sub.common.id.proxy.has_value() ||
        sub.common.id.proxy.value() != mIdentity.uuid) {
        ALOGE("%s sub effect already exist or mismatch %s", __func__, sub.toString().c_str());
        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
                                                                "illegalSubEffect");
    }

    // not create sub-effect yet
    std::get<SubEffectTupleIndex::HANDLE>(mSubEffects[sub.common.id]) = nullptr;
    std::get<SubEffectTupleIndex::DESCRIPTOR>(mSubEffects[sub.common.id]) = sub;
    // set the last added sub-effect to active before setOffloadParam()
    mActiveSub = sub.common.id;
    ALOGI("%s add %s to proxy %s flag %s", __func__, mActiveSub.toString().c_str(),
          mIdentity.toString().c_str(), sub.common.flags.toString().c_str());

    if (sub.common.flags.hwAcceleratorMode == Flags::HardwareAccelerator::TUNNEL) {
        mSubFlags.hwAcceleratorMode = Flags::HardwareAccelerator::TUNNEL;
    }

    // initial flag values before we know which sub-effect to active (with setOffloadParam)
    // same as HIDL EffectProxy flags
    mSubFlags.type = Flags::Type::INSERT;
    mSubFlags.insert = Flags::Insert::LAST;
    mSubFlags.volume = Flags::Volume::CTRL;
namespace android::effect {

    // set indication if any sub-effect indication was set
    mSubFlags.offloadIndication |= sub.common.flags.offloadIndication;
    mSubFlags.deviceIndication |= sub.common.flags.deviceIndication;
    mSubFlags.audioModeIndication |= sub.common.flags.audioModeIndication;
    mSubFlags.audioSourceIndication |= sub.common.flags.audioSourceIndication;

    // set bypass when all sub-effects are bypassing
    mSubFlags.bypass &= sub.common.flags.bypass;
    return ndk::ScopedAStatus::ok();
}

ndk::ScopedAStatus EffectProxy::create() {
    ALOGV("%s: %s", __func__, toString(mIdentity.type).c_str());
EffectProxy::EffectProxy(const AudioUuid& uuid, const std::vector<Descriptor>& descriptors,
                         const std::shared_ptr<IFactory>& factory)
    : mDescriptorCommon(buildDescriptorCommon(uuid, descriptors)),
      mSubEffects(
              [](const std::vector<Descriptor>& descs, const std::shared_ptr<IFactory>& factory) {
                  std::vector<SubEffect> subEffects;
                  ALOG_ASSERT(factory, "invalid EffectFactory handle");
                  ndk::ScopedAStatus status = ndk::ScopedAStatus::ok();

    for (auto& sub : mSubEffects) {
        auto& effectHandle = std::get<SubEffectTupleIndex::HANDLE>(sub.second);
        ALOGI("%s sub-effect %s", __func__, toString(sub.first.uuid).c_str());
        status = mFactory->createEffect(sub.first.uuid, &effectHandle);
        if (!status.isOk() || !effectHandle) {
            ALOGE("%s sub-effect failed %s", __func__, toString(sub.first.uuid).c_str());
            break;
                  for (const auto& desc : descs) {
                      SubEffect sub({.descriptor = desc});
                      status = factory->createEffect(desc.common.id.uuid, &sub.handle);
                      if (!status.isOk() || !sub.handle) {
                          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) {}

    // destroy all created effects if failure
    if (!status.isOk()) {
EffectProxy::~EffectProxy() {
    close();
    destroy();
    }
    return status;
    mSubEffects.clear();
}

ndk::ScopedAStatus EffectProxy::destroy() {
    ALOGV("%s: %s", __func__, toString(mIdentity.type).c_str());
    ALOGV("%s: %s", __func__,
          ::android::audio::utils::toString(mDescriptorCommon.id.type).c_str());
    return runWithAllSubEffects([&](std::shared_ptr<IEffect>& effect) {
        ndk::ScopedAStatus status = mFactory->destroyEffect(effect);
        if (status.isOk()) {
@@ -125,28 +77,24 @@ ndk::ScopedAStatus EffectProxy::destroy() {
    });
}

const IEffect::OpenEffectReturn* EffectProxy::getEffectReturnParam() {
    return &std::get<SubEffectTupleIndex::RETURN>(mSubEffects[mActiveSub]);
}

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 = std::get<SubEffectTupleIndex::DESCRIPTOR>(sub.second);
        ALOGI("%s: isOffload %d sub-effect: %s, flags %s", __func__, offload->isOffload,
              toString(desc.common.id.uuid).c_str(), desc.common.flags.toString().c_str());
        const auto& desc = sub.descriptor;
        return offload->isOffload ==
               (desc.common.flags.hwAcceleratorMode == Flags::HardwareAccelerator::TUNNEL);
    });
    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");
    }

    mActiveSub = itor->first;
    ALOGI("%s: active %soffload sub-effect: %s, flags %s", __func__,
          offload->isOffload ? "" : "non-", toString(mActiveSub.uuid).c_str(),
          std::get<SubEffectTupleIndex::DESCRIPTOR>(itor->second).common.flags.toString().c_str());
    mActiveSubIdx = std::distance(mSubEffects.begin(), itor);
    ALOGV("%s: active %soffload sub-effect %zu descriptor: %s", __func__,
          offload->isOffload ? "" : "non-", mActiveSubIdx,
          ::android::audio::utils::toString(mSubEffects[mActiveSubIdx].descriptor.common.id.uuid)
                  .c_str());
    return ndk::ScopedAStatus::ok();
}

@@ -154,20 +102,24 @@ ndk::ScopedAStatus EffectProxy::setOffloadParam(const effect_offload_param_t* of
ndk::ScopedAStatus EffectProxy::open(const Parameter::Common& common,
                                     const std::optional<Parameter::Specific>& specific,
                                     IEffect::OpenEffectReturn* ret __unused) {
    ALOGV("%s: %s", __func__, toString(mIdentity.type).c_str());
    ndk::ScopedAStatus status = ndk::ScopedAStatus::fromExceptionCodeWithMessage(
            EX_ILLEGAL_ARGUMENT, "nullEffectHandle");
    for (auto& sub : mSubEffects) {
        auto& effect = std::get<SubEffectTupleIndex::HANDLE>(sub.second);
        auto& openRet = std::get<SubEffectTupleIndex::RETURN>(sub.second);
        if (!effect || !(status = effect->open(common, specific, &openRet)).isOk()) {
            ALOGE("%s: failed to open UUID %s", __func__, toString(sub.first.uuid).c_str());
        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<StatusMQ>(openReturn.statusMQ);
        sub.effectMq.inputQ = std::make_shared<DataMQ>(openReturn.inputDataMQ);
        sub.effectMq.outputQ = std::make_shared<DataMQ>(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();
    }

@@ -175,38 +127,68 @@ ndk::ScopedAStatus EffectProxy::open(const Parameter::Common& common,
}

ndk::ScopedAStatus EffectProxy::close() {
    ALOGV("%s: %s", __func__, toString(mIdentity.type).c_str());
    return runWithAllSubEffects([&](std::shared_ptr<IEffect>& effect) {
        return effect->close();
    });
}

ndk::ScopedAStatus EffectProxy::getDescriptor(Descriptor* desc) {
    desc->common = mDescriptorCommon;
    desc->capability = mSubEffects[mActiveSubIdx].descriptor.capability;
    return ndk::ScopedAStatus::ok();
}

ndk::ScopedAStatus EffectProxy::buildDescriptor(const AudioUuid& uuid,
                                                const std::vector<Descriptor>& subEffectDescs,
                                                Descriptor* desc) {
    if (!desc) {
        ALOGE("%s: nuull descriptor pointer", __func__);
        ALOGE("%s: null descriptor pointer", __func__);
        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_NULL_POINTER, "nullptr");
    }

    auto& activeSubEffect = std::get<SubEffectTupleIndex::HANDLE>(mSubEffects[mActiveSub]);
    // return initial descriptor if no active sub-effect exist
    if (!activeSubEffect) {
        desc->common.id = mIdentity;
        desc->common.flags = mSubFlags;
        desc->common.name = "Proxy";
        desc->common.implementor = "AOSP";
    } else {
        *desc = std::get<SubEffectTupleIndex::DESCRIPTOR>(mSubEffects[mActiveSub]);
        desc->common.id = mIdentity;
    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");
    }

    ALOGI("%s with %s", __func__, desc->toString().c_str());
    desc->common = buildDescriptorCommon(uuid, subEffectDescs);
    return ndk::ScopedAStatus::ok();
}

Descriptor::Common EffectProxy::buildDescriptorCommon(
        const AudioUuid& uuid, const std::vector<Descriptor>& subEffectDescs) {
    Descriptor::Common common;
    for (const auto& desc : subEffectDescs) {
        if (desc.common.flags.hwAcceleratorMode == Flags::HardwareAccelerator::TUNNEL) {
            common.flags.hwAcceleratorMode = Flags::HardwareAccelerator::TUNNEL;
        }

        // initial flag values before we know which sub-effect to active (with setOffloadParam)
        // same as HIDL EffectProxy flags
        common.flags.type = Flags::Type::INSERT;
        common.flags.insert = Flags::Insert::LAST;
        common.flags.volume = Flags::Volume::CTRL;

        // set indication if any sub-effect indication was set
        common.flags.offloadIndication |= desc.common.flags.offloadIndication;
        common.flags.deviceIndication |= desc.common.flags.deviceIndication;
        common.flags.audioModeIndication |= desc.common.flags.audioModeIndication;
        common.flags.audioSourceIndication |= desc.common.flags.audioSourceIndication;
    }

    // copy type UUID from any of sub-effects, all sub-effects should have same type
    common.id.type = subEffectDescs[0].common.id.type;
    // replace implementation UUID with proxy UUID.
    common.id.uuid = uuid;
    common.id.proxy = std::nullopt;
    common.name = "Proxy";
    common.implementor = "AOSP";
    return common;
}

// Handle with active sub-effect first, only send to other sub-effects when success
ndk::ScopedAStatus EffectProxy::command(CommandId id) {
    ALOGV("%s: %s, command %s", __func__, toString(mIdentity.type).c_str(),
          android::internal::ToString(id).c_str());
    return runWithActiveSubEffectThenOthers(
            [&](const std::shared_ptr<IEffect>& effect) -> ndk::ScopedAStatus {
                return effect->command(id);
@@ -241,34 +223,30 @@ ndk::ScopedAStatus EffectProxy::runWithActiveSubEffectThenOthers(
        std::function<ndk::ScopedAStatus(const std::shared_ptr<IEffect>&)> const& func) {
    ndk::ScopedAStatus status = runWithActiveSubEffect(func);
    if (!status.isOk()) {
        return status;
        ALOGE("%s active sub-effect return error %s", __func__, status.getDescription().c_str());
    }

    // proceed with others if active sub-effect success
    for (const auto& sub : mSubEffects) {
        auto& effect = std::get<SubEffectTupleIndex::HANDLE>(sub.second);
        if (sub.first != mActiveSub) {
            if (!effect) {
    // proceed with others
    for (size_t i = 0; i < mSubEffects.size() && i != mActiveSubIdx; i++) {
        if (!mSubEffects[i].handle) {
            ALOGE("%s null sub-effect interface for %s", __func__,
                      sub.first.toString().c_str());
                  mSubEffects[i].descriptor.common.id.uuid.toString().c_str());
            continue;
        }
            func(effect);
        }
        func(mSubEffects[i].handle);
    }
    return status;
}

ndk::ScopedAStatus EffectProxy::runWithActiveSubEffect(
        std::function<ndk::ScopedAStatus(const std::shared_ptr<IEffect>&)> const& func) {
    auto& effect = std::get<SubEffectTupleIndex::HANDLE>(mSubEffects[mActiveSub]);
    if (!effect) {
    if (!mSubEffects[mActiveSubIdx].handle) {
        ALOGE("%s null active sub-effect interface, active %s", __func__,
              mActiveSub.toString().c_str());
              mSubEffects[mActiveSubIdx].descriptor.toString().c_str());
        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_NULL_POINTER,
                                                                "activeSubEffectNull");
    }
    return func(effect);
    return func(mSubEffects[mActiveSubIdx].handle);
}

ndk::ScopedAStatus EffectProxy::runWithAllSubEffects(
@@ -276,12 +254,11 @@ ndk::ScopedAStatus EffectProxy::runWithAllSubEffects(
    ndk::ScopedAStatus status = ndk::ScopedAStatus::ok();
    // proceed with others if active sub-effect success
    for (auto& sub : mSubEffects) {
        auto& effect = std::get<SubEffectTupleIndex::HANDLE>(sub.second);
        if (!effect) {
            ALOGW("%s null sub-effect interface for %s", __func__, sub.first.toString().c_str());
        if (!sub.handle) {
            ALOGW("%s null sub-effect interface %s", __func__, sub.descriptor.toString().c_str());
            continue;
        }
        ndk::ScopedAStatus temp = func(effect);
        ndk::ScopedAStatus temp = func(sub.handle);
        if (!temp.isOk()) {
            status = ndk::ScopedAStatus::fromStatus(temp.getStatus());
        }
@@ -289,5 +266,34 @@ ndk::ScopedAStatus EffectProxy::runWithAllSubEffects(
    return status;
}

} // namespace effect
} // namespace android
bool EffectProxy::isBypassing() const {
    return mSubEffects[mActiveSubIdx].descriptor.common.flags.bypass;
}

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<IEffect>& 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 ? mSubEffects[i].handle.get() : nullptr,
                            mSubEffects[i].descriptor.toString().c_str());
    }
    return ss;
}

} // namespace android::effect
+62 −41

File changed.

Preview size limit exceeded, changes collapsed.

Loading