Merge "Downmix : Add libeffect implementation" am: f1a9007f3d am: ecc90a6127 am: ba1af68dcd

Original change: https://android-review.googlesource.com/c/platform/frameworks/av/+/2353846

Change-Id: If3e48fae8232a0db9ef1d5d41ada85004f889ea7
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/media/libeffects/downmix/Android.bp b/media/libeffects/downmix/Android.bp
index abe622d..742626c 100644
--- a/media/libeffects/downmix/Android.bp
+++ b/media/libeffects/downmix/Android.bp
@@ -47,3 +47,29 @@
         "libhardware_headers",
     ],
 }
+
+cc_library_shared {
+    name: "libdownmixaidl",
+    srcs: [
+        "aidl/EffectDownmix.cpp",
+        "aidl/DownmixContext.cpp",
+        ":effectCommonFile",
+    ],
+    defaults: [
+        "aidlaudioservice_defaults",
+        "latest_android_hardware_audio_effect_ndk_shared",
+        "latest_android_media_audio_common_types_ndk_shared",
+    ],
+    header_libs: [
+        "libaudioeffects",
+        "libhardware_headers"
+    ],
+    shared_libs: [
+        "libaudioutils",
+        "libcutils",
+        "liblog",
+    ],
+    visibility: [
+        "//hardware/interfaces/audio/aidl/default",
+    ],
+}
diff --git a/media/libeffects/downmix/aidl/DownmixContext.cpp b/media/libeffects/downmix/aidl/DownmixContext.cpp
new file mode 100644
index 0000000..6869689
--- /dev/null
+++ b/media/libeffects/downmix/aidl/DownmixContext.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#define LOG_TAG "AHAL_DownmixContext"
+
+#include <android-base/logging.h>
+
+#include "DownmixContext.h"
+
+using aidl::android::hardware::audio::effect::IEffect;
+using ::android::hardware::audio::common::getChannelCount;
+
+namespace aidl::android::hardware::audio::effect {
+
+DownmixContext::DownmixContext(int statusDepth, const Parameter::Common& common)
+    : EffectContext(statusDepth, common) {
+    LOG(DEBUG) << __func__;
+    mState = DOWNMIX_STATE_UNINITIALIZED;
+    init_params(common);
+}
+
+DownmixContext::~DownmixContext() {
+    LOG(DEBUG) << __func__;
+    mState = DOWNMIX_STATE_UNINITIALIZED;
+}
+
+RetCode DownmixContext::enable() {
+    LOG(DEBUG) << __func__;
+    if (mState != DOWNMIX_STATE_INITIALIZED) {
+        return RetCode::ERROR_EFFECT_LIB_ERROR;
+    }
+    mState = DOWNMIX_STATE_ACTIVE;
+    return RetCode::SUCCESS;
+}
+
+RetCode DownmixContext::disable() {
+    LOG(DEBUG) << __func__;
+    if (mState != DOWNMIX_STATE_ACTIVE) {
+        return RetCode::ERROR_EFFECT_LIB_ERROR;
+    }
+    mState = DOWNMIX_STATE_INITIALIZED;
+    return RetCode::SUCCESS;
+}
+
+void DownmixContext::reset() {
+    LOG(DEBUG) << __func__;
+    disable();
+    resetBuffer();
+}
+
+IEffect::Status DownmixContext::lvmProcess(float* in, float* out, int samples) {
+    LOG(DEBUG) << __func__ << " in " << in << " out " << out << " sample " << samples;
+    IEffect::Status status = {EX_ILLEGAL_ARGUMENT, 0, 0};
+
+    if (in == nullptr || out == nullptr || getInputFrameSize() != getOutputFrameSize() ||
+        getInputFrameSize() == 0) {
+        return status;
+    }
+
+    status = {EX_ILLEGAL_STATE, 0, 0};
+    if (mState == DOWNMIX_STATE_UNINITIALIZED) {
+        LOG(ERROR) << __func__ << "Trying to use an uninitialized downmixer";
+        return status;
+    } else if (mState == DOWNMIX_STATE_INITIALIZED) {
+        LOG(ERROR) << __func__ << "Trying to use a non-configured downmixer";
+        return status;
+    }
+
+    LOG(DEBUG) << __func__ << " start processing";
+    bool accumulate = false;
+    int frames = samples * sizeof(float) / getInputFrameSize();
+    if (mType == Downmix::Type::STRIP) {
+        int inputChannelCount = getChannelCount(mChMask);
+        while (frames) {
+            if (accumulate) {
+                out[0] = std::clamp(out[0] + in[0], -1.f, 1.f);
+                out[1] = std::clamp(out[1] + in[1], -1.f, 1.f);
+            } else {
+                out[0] = in[0];
+                out[1] = in[1];
+            }
+            in += inputChannelCount;
+            out += 2;
+            frames--;
+        }
+    } else {
+        int chMask = mChMask.get<AudioChannelLayout::layoutMask>();
+        if (!mChannelMix.process(in, out, frames, accumulate, (audio_channel_mask_t)chMask)) {
+            LOG(ERROR) << "Multichannel configuration " << mChMask.toString()
+                       << " is not supported";
+            return status;
+        }
+    }
+    LOG(DEBUG) << __func__ << " done processing";
+    return {STATUS_OK, samples, samples};
+}
+
+void DownmixContext::init_params(const Parameter::Common& common) {
+    // when configuring the effect, do not allow a blank or unsupported channel mask
+    AudioChannelLayout channelMask = common.input.base.channelMask;
+    if (isChannelMaskValid(channelMask)) {
+        LOG(ERROR) << "Downmix_Configure error: input channel mask " << channelMask.toString()
+                   << " not supported";
+    } else {
+        mType = Downmix::Type::FOLD;
+        mChMask = channelMask;
+        mState = DOWNMIX_STATE_INITIALIZED;
+    }
+}
+
+bool DownmixContext::isChannelMaskValid(AudioChannelLayout channelMask) {
+    if (channelMask.getTag() == AudioChannelLayout::layoutMask) return false;
+    int chMask = channelMask.get<AudioChannelLayout::layoutMask>();
+    // check against unsupported channels (up to FCC_26)
+    constexpr uint32_t MAXIMUM_CHANNEL_MASK = AudioChannelLayout::LAYOUT_22POINT2 |
+                                              AudioChannelLayout::CHANNEL_FRONT_WIDE_LEFT |
+                                              AudioChannelLayout::CHANNEL_FRONT_WIDE_RIGHT;
+    if (chMask & ~MAXIMUM_CHANNEL_MASK) {
+        LOG(ERROR) << "Unsupported channels in " << (chMask & ~MAXIMUM_CHANNEL_MASK);
+        return false;
+    }
+    return true;
+}
+
+}  // namespace aidl::android::hardware::audio::effect
diff --git a/media/libeffects/downmix/aidl/DownmixContext.h b/media/libeffects/downmix/aidl/DownmixContext.h
new file mode 100644
index 0000000..8a244ac
--- /dev/null
+++ b/media/libeffects/downmix/aidl/DownmixContext.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include "effect-impl/EffectContext.h"
+
+#include <audio_utils/ChannelMix.h>
+
+namespace aidl::android::hardware::audio::effect {
+
+using media::audio::common::AudioChannelLayout;
+using media::audio::common::AudioDeviceDescription;
+
+enum DownmixState {
+    DOWNMIX_STATE_UNINITIALIZED,
+    DOWNMIX_STATE_INITIALIZED,
+    DOWNMIX_STATE_ACTIVE,
+};
+
+class DownmixContext final : public EffectContext {
+  public:
+    DownmixContext(int statusDepth, const Parameter::Common& common);
+    ~DownmixContext();
+    RetCode enable();
+    RetCode disable();
+    void reset();
+
+    RetCode setDmType(Downmix::Type type) {
+        mType = type;
+        return RetCode::SUCCESS;
+    }
+    Downmix::Type getDmType() const { return mType; }
+
+    RetCode setVolumeStereo(const Parameter::VolumeStereo& volumeStereo) override {
+        // FIXME change volume
+        mVolumeStereo = volumeStereo;
+        return RetCode::SUCCESS;
+    }
+    Parameter::VolumeStereo getVolumeStereo() override { return mVolumeStereo; }
+
+    RetCode setOutputDevice(const AudioDeviceDescription& device) override {
+        // FIXME change type if playing on headset vs speaker
+        mOutputDevice = device;
+        return RetCode::SUCCESS;
+    }
+    AudioDeviceDescription getOutputDevice() { return mOutputDevice; }
+
+    IEffect::Status lvmProcess(float* in, float* out, int samples);
+
+  private:
+    DownmixState mState;
+    Downmix::Type mType;
+    AudioChannelLayout mChMask;
+    ::android::audio_utils::channels::ChannelMix mChannelMix;
+
+    // Common Params
+    AudioDeviceDescription mOutputDevice;
+    Parameter::VolumeStereo mVolumeStereo;
+
+    void init_params(const Parameter::Common& common);
+    bool isChannelMaskValid(AudioChannelLayout channelMask);
+};
+
+}  // namespace aidl::android::hardware::audio::effect
diff --git a/media/libeffects/downmix/aidl/EffectDownmix.cpp b/media/libeffects/downmix/aidl/EffectDownmix.cpp
new file mode 100644
index 0000000..17d0736
--- /dev/null
+++ b/media/libeffects/downmix/aidl/EffectDownmix.cpp
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#define LOG_TAG "AHAL_DownmixImpl"
+
+#include <android-base/logging.h>
+
+#include "EffectDownmix.h"
+
+using aidl::android::hardware::audio::effect::Descriptor;
+using aidl::android::hardware::audio::effect::DownmixImpl;
+using aidl::android::hardware::audio::effect::IEffect;
+using aidl::android::hardware::audio::effect::kDownmixImplUUID;
+using aidl::android::hardware::audio::effect::kDownmixTypeUUID;
+using aidl::android::media::audio::common::AudioUuid;
+
+extern "C" binder_exception_t createEffect(const AudioUuid* in_impl_uuid,
+                                           std::shared_ptr<IEffect>* instanceSpp) {
+    if (!in_impl_uuid || *in_impl_uuid != kDownmixImplUUID) {
+        LOG(ERROR) << __func__ << "uuid not supported";
+        return EX_ILLEGAL_ARGUMENT;
+    }
+    if (instanceSpp) {
+        *instanceSpp = ndk::SharedRefBase::make<DownmixImpl>();
+        LOG(DEBUG) << __func__ << " instance " << instanceSpp->get() << " created";
+        return EX_NONE;
+    } else {
+        LOG(ERROR) << __func__ << " invalid input parameter!";
+        return EX_ILLEGAL_ARGUMENT;
+    }
+}
+
+extern "C" binder_exception_t queryEffect(const AudioUuid* in_impl_uuid, Descriptor* _aidl_return) {
+    if (!in_impl_uuid || *in_impl_uuid != kDownmixImplUUID) {
+        LOG(ERROR) << __func__ << "uuid not supported";
+        return EX_ILLEGAL_ARGUMENT;
+    }
+    *_aidl_return = DownmixImpl::kDescriptor;
+    return EX_NONE;
+}
+
+namespace aidl::android::hardware::audio::effect {
+
+const std::string DownmixImpl::kEffectName = "Multichannel Downmix To Stereo";
+const Descriptor DownmixImpl::kDescriptor = {
+        .common = {
+                .id = {.type = kDownmixTypeUUID, .uuid = kDownmixImplUUID, .proxy = std::nullopt},
+                .flags = {.type = Flags::Type::INSERT, .insert = Flags::Insert::FIRST},
+                .name = DownmixImpl::kEffectName,
+                .implementor = "The Android Open Source Project"}};
+
+ndk::ScopedAStatus DownmixImpl::getDescriptor(Descriptor* _aidl_return) {
+    RETURN_IF(!_aidl_return, EX_ILLEGAL_ARGUMENT, "Parameter:nullptr");
+    LOG(DEBUG) << __func__ << kDescriptor.toString();
+    *_aidl_return = kDescriptor;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus DownmixImpl::setParameterCommon(const Parameter& param) {
+    RETURN_IF(!mContext, EX_NULL_POINTER, "nullContext");
+
+    auto tag = param.getTag();
+    switch (tag) {
+        case Parameter::common:
+            RETURN_IF(mContext->setCommon(param.get<Parameter::common>()) != RetCode::SUCCESS,
+                      EX_ILLEGAL_ARGUMENT, "setCommFailed");
+            break;
+        case Parameter::deviceDescription:
+            RETURN_IF(mContext->setOutputDevice(param.get<Parameter::deviceDescription>()) !=
+                              RetCode::SUCCESS,
+                      EX_ILLEGAL_ARGUMENT, "setDeviceFailed");
+            break;
+        case Parameter::mode:
+            RETURN_IF(mContext->setAudioMode(param.get<Parameter::mode>()) != RetCode::SUCCESS,
+                      EX_ILLEGAL_ARGUMENT, "setModeFailed");
+            break;
+        case Parameter::source:
+            RETURN_IF(mContext->setAudioSource(param.get<Parameter::source>()) != RetCode::SUCCESS,
+                      EX_ILLEGAL_ARGUMENT, "setSourceFailed");
+            break;
+        case Parameter::volumeStereo:
+            RETURN_IF(mContext->setVolumeStereo(param.get<Parameter::volumeStereo>()) !=
+                              RetCode::SUCCESS,
+                      EX_ILLEGAL_ARGUMENT, "setVolumeStereoFailed");
+            break;
+        default: {
+            LOG(ERROR) << __func__ << " unsupportedParameterTag " << toString(tag);
+            return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
+                                                                    "commonParamNotSupported");
+        }
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus DownmixImpl::commandImpl(CommandId command) {
+    RETURN_IF(!mContext, EX_NULL_POINTER, "nullContext");
+    switch (command) {
+        case CommandId::START:
+            mContext->enable();
+            break;
+        case CommandId::STOP:
+            mContext->disable();
+            break;
+        case CommandId::RESET:
+            mContext->reset();
+            break;
+        default:
+            LOG(ERROR) << __func__ << " commandId " << toString(command) << " not supported";
+            return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
+                                                                    "commandIdNotSupported");
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus DownmixImpl::setParameterSpecific(const Parameter::Specific& specific) {
+    RETURN_IF(Parameter::Specific::downmix != specific.getTag(), EX_ILLEGAL_ARGUMENT,
+              "EffectNotSupported");
+    RETURN_IF(!mContext, EX_NULL_POINTER, "nullContext");
+
+    auto& dmParam = specific.get<Parameter::Specific::downmix>();
+    auto tag = dmParam.getTag();
+
+    switch (tag) {
+        case Downmix::type: {
+            RETURN_IF(mContext->setDmType(dmParam.get<Downmix::type>()) != RetCode::SUCCESS,
+                      EX_ILLEGAL_ARGUMENT, "setTypeFailed");
+            return ndk::ScopedAStatus::ok();
+        }
+        default: {
+            LOG(ERROR) << __func__ << " unsupported tag: " << toString(tag);
+            return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
+                                                                    "DownmixTagNotSupported");
+        }
+    }
+}
+
+ndk::ScopedAStatus DownmixImpl::getParameterSpecific(const Parameter::Id& id,
+                                                     Parameter::Specific* specific) {
+    RETURN_IF(!specific, EX_NULL_POINTER, "nullPtr");
+    auto tag = id.getTag();
+    RETURN_IF(Parameter::Id::downmixTag != tag, EX_ILLEGAL_ARGUMENT, "wrongIdTag");
+    auto dmId = id.get<Parameter::Id::downmixTag>();
+    auto dmIdTag = dmId.getTag();
+    switch (dmIdTag) {
+        case Downmix::Id::commonTag:
+            return getParameterDownmix(dmId.get<Downmix::Id::commonTag>(), specific);
+        default:
+            LOG(ERROR) << __func__ << " unsupported tag: " << toString(dmIdTag);
+            return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
+                                                                    "DownmixTagNotSupported");
+    }
+}
+
+ndk::ScopedAStatus DownmixImpl::getParameterDownmix(const Downmix::Tag& tag,
+                                                    Parameter::Specific* specific) {
+    RETURN_IF(!mContext, EX_NULL_POINTER, "nullContext");
+
+    Downmix dmParam;
+    switch (tag) {
+        case Downmix::type: {
+            dmParam.set<Downmix::type>(mContext->getDmType());
+            break;
+        }
+        default: {
+            LOG(ERROR) << __func__ << " unsupported tag: " << toString(tag);
+            return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
+                                                                    "DownmixTagNotSupported");
+        }
+    }
+
+    specific->set<Parameter::Specific::downmix>(dmParam);
+    return ndk::ScopedAStatus::ok();
+}
+
+std::shared_ptr<EffectContext> DownmixImpl::createContext(const Parameter::Common& common) {
+    if (mContext) {
+        LOG(DEBUG) << __func__ << " context already exist";
+        return mContext;
+    }
+
+    mContext = std::make_shared<DownmixContext>(1 /* statusFmqDepth */, common);
+    return mContext;
+}
+
+RetCode DownmixImpl::releaseContext() {
+    if (mContext) {
+        mContext.reset();
+    }
+    return RetCode::SUCCESS;
+}
+
+// Processing method running in EffectWorker thread.
+IEffect::Status DownmixImpl::effectProcessImpl(float* in, float* out, int sampleToProcess) {
+    if (!mContext) {
+        LOG(ERROR) << __func__ << " nullContext";
+        return {EX_NULL_POINTER, 0, 0};
+    }
+    return mContext->lvmProcess(in, out, sampleToProcess);
+}
+
+}  // namespace aidl::android::hardware::audio::effect
diff --git a/media/libeffects/downmix/aidl/EffectDownmix.h b/media/libeffects/downmix/aidl/EffectDownmix.h
new file mode 100644
index 0000000..d590133
--- /dev/null
+++ b/media/libeffects/downmix/aidl/EffectDownmix.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/audio/effect/BnEffect.h>
+#include <audio_effects/effect_downmix.h>
+
+#include "DownmixContext.h"
+#include "effect-impl/EffectImpl.h"
+#include "effect-impl/EffectUUID.h"
+
+namespace aidl::android::hardware::audio::effect {
+
+class DownmixImpl final : public EffectImpl {
+  public:
+    static const std::string kEffectName;
+    static const Descriptor kDescriptor;
+    DownmixImpl() { LOG(DEBUG) << __func__; }
+    ~DownmixImpl() {
+        cleanUp();
+        LOG(DEBUG) << __func__;
+    }
+
+    ndk::ScopedAStatus commandImpl(CommandId command) override;
+    ndk::ScopedAStatus getDescriptor(Descriptor* _aidl_return) override;
+    ndk::ScopedAStatus setParameterCommon(const Parameter& param) override;
+    ndk::ScopedAStatus setParameterSpecific(const Parameter::Specific& specific) override;
+    ndk::ScopedAStatus getParameterSpecific(const Parameter::Id& id,
+                                            Parameter::Specific* specific) override;
+    IEffect::Status effectProcessImpl(float* in, float* out, int process) override;
+    std::shared_ptr<EffectContext> createContext(const Parameter::Common& common) override;
+    RetCode releaseContext() override;
+
+    std::shared_ptr<EffectContext> getContext() override { return mContext; }
+    std::string getEffectName() override { return kEffectName; }
+
+  private:
+    std::shared_ptr<DownmixContext> mContext;
+    ndk::ScopedAStatus getParameterDownmix(const Downmix::Tag& tag, Parameter::Specific* specific);
+};
+}  // namespace aidl::android::hardware::audio::effect