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

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

AIDL effect: Add Equalizer parameters implementation and vts

Bug: 238913361
Test: atest VtsHalAudioEffectTargetTest
atest VtsHalAudioEffectFactoryTargetTest
atest VtsHalEqualizerTargetTest

Change-Id: I94b2283ca2aa0e45715e1c9ac3ea6ad809ec2a2c
parent e3559444
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -116,6 +116,6 @@ cc_binary {
cc_library_headers {
    name: "libaudioaidl_headers",
    export_include_dirs: ["include"],
    vendor: true,
    vendor_available: true,
    host_supported: true,
}
+7 −18
Original line number Diff line number Diff line
@@ -28,11 +28,11 @@ EffectThread::EffectThread() {
}

EffectThread::~EffectThread() {
    destroy();
    destroyThread();
    LOG(DEBUG) << __func__ << " done";
};

RetCode EffectThread::create(const std::string& name, const int priority) {
RetCode EffectThread::createThread(const std::string& name, const int priority) {
    if (mThread.joinable()) {
        LOG(WARNING) << __func__ << " thread already created, no-op";
        return RetCode::SUCCESS;
@@ -44,7 +44,7 @@ RetCode EffectThread::create(const std::string& name, const int priority) {
    return RetCode::SUCCESS;
}

RetCode EffectThread::destroy() {
RetCode EffectThread::destroyThread() {
    {
        std::lock_guard lg(mMutex);
        mStop = mExit = true;
@@ -58,10 +58,10 @@ RetCode EffectThread::destroy() {
    return RetCode::SUCCESS;
}

RetCode EffectThread::start() {
RetCode EffectThread::startThread() {
    if (!mThread.joinable()) {
        LOG(ERROR) << __func__ << " thread already destroyed";
        return RetCode::ERROR;
        return RetCode::ERROR_THREAD;
    }

    {
@@ -78,10 +78,10 @@ RetCode EffectThread::start() {
    return RetCode::SUCCESS;
}

RetCode EffectThread::stop() {
RetCode EffectThread::stopThread() {
    if (!mThread.joinable()) {
        LOG(ERROR) << __func__ << " thread already destroyed";
        return RetCode::ERROR;
        return RetCode::ERROR_THREAD;
    }

    {
@@ -117,15 +117,4 @@ void EffectThread::threadLoop() {
    }
}

std::string toString(RetCode& code) {
    switch (code) {
        case RetCode::SUCCESS:
            return "SUCCESS";
        case RetCode::ERROR:
            return "ERROR";
        default:
            return "EnumError";
    }
}

}  // namespace aidl::android::hardware::audio::effect
+118 −52
Original line number Diff line number Diff line
@@ -16,10 +16,12 @@

#define LOG_TAG "AHAL_EqualizerSw"
#include <Utils.h>
#include <android-base/logging.h>
#include <algorithm>
#include <unordered_set>

#include "effect-impl/EffectUUID.h"
#include <android-base/logging.h>
#include <fmq/AidlMessageQueue.h>

#include "equalizer-impl/EqualizerSw.h"

using android::hardware::audio::common::getFrameSizeInBytes;
@@ -68,20 +70,26 @@ ndk::ScopedAStatus EqualizerSw::open(const Parameter::Common& common,
    auto& output = common.output;
    size_t inputFrameSize = getFrameSizeInBytes(input.base.format, input.base.channelMask);
    size_t outputFrameSize = getFrameSizeInBytes(output.base.format, output.base.channelMask);
    if (!createFmq(1, input.frameCount * inputFrameSize, output.frameCount * outputFrameSize,
                   _aidl_return)) {
    mContext = std::make_shared<EqualizerSwContext>(1, input.frameCount * inputFrameSize,
                                                    output.frameCount * outputFrameSize);
    if (!mContext) {
        LOG(ERROR) << __func__ << " created EqualizerSwContext failed";
        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_UNSUPPORTED_OPERATION,
                                                                "FailedToCreateFmq");
    }
    setContext(mContext);

    // create the worker thread
    if (RetCode::SUCCESS != mWorker->create(LOG_TAG)) {
    if (RetCode::SUCCESS != createThread(LOG_TAG)) {
        LOG(ERROR) << __func__ << " created worker thread failed";
        destroyFmq();
        mContext.reset();
        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_UNSUPPORTED_OPERATION,
                                                                "FailedToCreateFmq");
                                                                "FailedToCreateWorker");
    }

    _aidl_return->statusMQ = mContext->getStatusFmq()->dupeDesc();
    _aidl_return->inputDataMQ = mContext->getInputDataFmq()->dupeDesc();
    _aidl_return->outputDataMQ = mContext->getOutputDataFmq()->dupeDesc();
    mState = State::IDLE;
    return ndk::ScopedAStatus::ok();
}
@@ -98,8 +106,9 @@ ndk::ScopedAStatus EqualizerSw::close() {

    // stop the worker thread
    mState = State::INIT;
    mWorker->destroy();
    destroyFmq();
    destroyThread();
    mContext.reset();

    LOG(DEBUG) << __func__;
    return ndk::ScopedAStatus::ok();
}
@@ -121,19 +130,19 @@ ndk::ScopedAStatus EqualizerSw::command(CommandId in_commandId) {
        case CommandId::START:
            // start processing.
            mState = State::PROCESSING;
            mWorker->start();
            startThread();
            LOG(DEBUG) << __func__ << " state: " << toString(mState);
            return ndk::ScopedAStatus::ok();
        case CommandId::STOP:
            // stop processing.
            mState = State::IDLE;
            mWorker->stop();
            stopThread();
            LOG(DEBUG) << __func__ << " state: " << toString(mState);
            return ndk::ScopedAStatus::ok();
        case CommandId::RESET:
            // TODO: reset buffer status.
            mState = State::IDLE;
            mWorker->stop();
            stopThread();
            LOG(DEBUG) << __func__ << " state: " << toString(mState);
            return ndk::ScopedAStatus::ok();
        default:
@@ -173,23 +182,27 @@ ndk::ScopedAStatus EqualizerSw::getParameter(const Parameter::Id& in_paramId,
            LOG(DEBUG) << __func__ << " get: " << _aidl_return->toString();
            return ndk::ScopedAStatus::ok();
        }
        case Parameter::Id::specificTag: {
            auto& id = in_paramId.get<Parameter::Id::specificTag>();
            if (id != Parameter::Specific::equalizer) {
                LOG(ERROR) << " unsupported parameter Id: " << in_paramId.toString();
                return ndk::ScopedAStatus::fromExceptionCodeWithMessage(
                        EX_ILLEGAL_ARGUMENT, "Parameter::IdNotSupported");
            }
        case Parameter::Id::specificId: {
            auto& id = in_paramId.get<Parameter::Id::specificId>();
            Parameter::Specific specific;
            specific.set<Parameter::Specific::equalizer>(mEqualizerParam);
            ndk::ScopedAStatus status = getSpecificParameter(id, &specific);
            if (!status.isOk()) {
                LOG(ERROR) << __func__
                           << " getSpecificParameter error: " << status.getDescription();
                return status;
            }
            _aidl_return->set<Parameter::specific>(specific);
            LOG(DEBUG) << __func__ << _aidl_return->toString();
            return ndk::ScopedAStatus::ok();
        }
        default:
            return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
                                                                    "Parameter::IdNotSupported");
        case Parameter::Id::vendorTag: {
            LOG(DEBUG) << __func__ << " noop for vendor tag now";
            return ndk::ScopedAStatus::ok();
        }
    }
    LOG(ERROR) << " unsupported tag: " << toString(tag);
    return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
                                                            "Parameter:IdNotSupported");
}

ndk::ScopedAStatus EqualizerSw::getState(State* _aidl_return) {
@@ -198,35 +211,12 @@ ndk::ScopedAStatus EqualizerSw::getState(State* _aidl_return) {
}

/// Private methods.
bool EqualizerSw::createFmq(int statusDepth, int inBufferSize, int outBufferSize,
                            OpenEffectReturn* ret) {
    mStatusMQ = std::make_unique<StatusMQ>(statusDepth, true /*configureEventFlagWord*/);
    mInputMQ = std::make_unique<DataMQ>(inBufferSize);
    mOutputMQ = std::make_unique<DataMQ>(outBufferSize);

    if (!mStatusMQ->isValid() || !mInputMQ->isValid() || !mOutputMQ->isValid()) {
        LOG(ERROR) << __func__ << " created invalid FMQ";
        return false;
    }
    ret->statusMQ = mStatusMQ->dupeDesc();
    ret->inputDataMQ = mInputMQ->dupeDesc();
    ret->outputDataMQ = mOutputMQ->dupeDesc();
    return true;
}

void EqualizerSw::destroyFmq() {
    mStatusMQ.reset(nullptr);
    mInputMQ.reset(nullptr);
    mOutputMQ.reset(nullptr);
}

ndk::ScopedAStatus EqualizerSw::setCommonParameter(const Parameter::Common& common) {
    mCommonParam = common;
    LOG(DEBUG) << __func__ << " set: " << mCommonParam.toString();
    return ndk::ScopedAStatus::ok();
}

// TODO: implementation need change to save all parameters.
ndk::ScopedAStatus EqualizerSw::setSpecificParameter(const Parameter::Specific& specific) {
    if (Parameter::Specific::equalizer != specific.getTag()) {
        LOG(ERROR) << " unsupported effect: " << specific.toString();
@@ -234,10 +224,77 @@ ndk::ScopedAStatus EqualizerSw::setSpecificParameter(const Parameter::Specific&
                                                                "EffectNotSupported");
    }

    mEqualizerParam = specific.get<Parameter::Specific::equalizer>();
    LOG(DEBUG) << __func__ << mEqualizerParam.toString();
    auto& eqParam = specific.get<Parameter::Specific::equalizer>();
    auto tag = eqParam.getTag();
    switch (tag) {
        case Equalizer::bandLevels: {
            auto& bandLevels = eqParam.get<Equalizer::bandLevels>();
            const auto& [minItem, maxItem] = std::minmax_element(
                    bandLevels.begin(), bandLevels.end(),
                    [](const auto& a, const auto& b) { return a.index < b.index; });
            if (bandLevels.size() >= NUM_OF_BANDS || minItem->index < 0 ||
                maxItem->index >= NUM_OF_BANDS) {
                LOG(ERROR) << " bandLevels " << bandLevels.size() << "minIndex " << minItem->index
                           << "maxIndex " << maxItem->index << " illegal ";
                return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
                                                                        "ExceedMaxBandNum");
            }
            mBandLevels = bandLevels;
            return ndk::ScopedAStatus::ok();
        }
        case Equalizer::preset: {
            int preset = eqParam.get<Equalizer::preset>();
            if (preset < 0 || preset >= NUM_OF_PRESETS) {
                LOG(ERROR) << " preset: " << preset << " invalid";
                return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
                                                                        "ExceedMaxBandNum");
            }
            mPreset = preset;
            LOG(DEBUG) << __func__ << " preset set to " << mPreset;
            return ndk::ScopedAStatus::ok();
        }
        case Equalizer::vendor: {
            LOG(DEBUG) << __func__ << " noop for vendor tag now";
            return ndk::ScopedAStatus::ok();
        }
    }

    LOG(ERROR) << __func__ << " unsupported eq param tag: " << toString(tag);
    return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
                                                            "ParamNotSupported");
}

ndk::ScopedAStatus EqualizerSw::getSpecificParameter(Parameter::Specific::Id id,
                                                     Parameter::Specific* specific) {
    Equalizer eqParam;
    auto tag = id.getTag();
    if (tag != Parameter::Specific::Id::equalizerTag) {
        LOG(ERROR) << " invalid tag: " << toString(tag);
        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
                                                                "UnsupportedTag");
    }
    auto eqTag = id.get<Parameter::Specific::Id::equalizerTag>();
    switch (eqTag) {
        case Equalizer::bandLevels: {
            eqParam.set<Equalizer::bandLevels>(mBandLevels);
            specific->set<Parameter::Specific::equalizer>(eqParam);
            return ndk::ScopedAStatus::ok();
        }
        case Equalizer::preset: {
            eqParam.set<Equalizer::preset>(mPreset);
            LOG(DEBUG) << __func__ << " preset " << mPreset;
            specific->set<Parameter::Specific::equalizer>(eqParam);
            return ndk::ScopedAStatus::ok();
        }
        case Equalizer::vendor: {
            LOG(DEBUG) << __func__ << " noop for vendor tag now";
            return ndk::ScopedAStatus::ok();
        }
    }
    LOG(ERROR) << __func__ << " unsupported eq param: " << toString(eqTag);
    return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
                                                            "ParamNotSupported");
}

void EqualizerSw::cleanUp() {
    if (State::PROCESSING == mState) {
@@ -248,9 +305,18 @@ void EqualizerSw::cleanUp() {
    }
}

// Processing method running in worker thread.
void EqualizerSwWorker::process() {
    // TODO: add EQ processing with FMQ, should wait until data available before data processing.
IEffect::Status EqualizerSw::status(binder_status_t status, size_t consumed, size_t produced) {
    IEffect::Status ret;
    ret.status = status;
    ret.fmqByteConsumed = consumed;
    ret.fmqByteProduced = produced;
    return ret;
}

// Processing method running in EffectWorker thread.
IEffect::Status EqualizerSw::effectProcessImpl() {
    // TODO: get data buffer and process.
    return status(STATUS_OK, mContext->availableToRead(), mContext->availableToWrite());
}

}  // namespace aidl::android::hardware::audio::effect
+65 −0
Original line number Diff line number Diff line
/*
 * 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 <cstdint>
#include <memory>
#include <utility>
#include <vector>

#include <aidl/android/hardware/audio/effect/BnEffect.h>
#include <fmq/AidlMessageQueue.h>

namespace aidl::android::hardware::audio::effect {

class EffectContext {
  public:
    typedef ::android::AidlMessageQueue<
            IEffect::Status, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>
            StatusMQ;
    typedef ::android::AidlMessageQueue<
            int8_t, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>
            DataMQ;

    EffectContext(size_t statusDepth, size_t inBufferSize, size_t outBufferSize) {
        mStatusMQ = std::make_shared<StatusMQ>(statusDepth, true /*configureEventFlagWord*/);
        mInputMQ = std::make_shared<DataMQ>(inBufferSize);
        mOutputMQ = std::make_shared<DataMQ>(outBufferSize);

        if (!mStatusMQ->isValid() || !mInputMQ->isValid() || !mOutputMQ->isValid()) {
            LOG(ERROR) << __func__ << " created invalid FMQ";
        }
        mWorkBuffer.reserve(std::max(inBufferSize, outBufferSize));
    };

    std::shared_ptr<StatusMQ> getStatusFmq() { return mStatusMQ; };
    std::shared_ptr<DataMQ> getInputDataFmq() { return mInputMQ; };
    std::shared_ptr<DataMQ> getOutputDataFmq() { return mOutputMQ; };

    int8_t* getWorkBuffer() { return static_cast<int8_t*>(mWorkBuffer.data()); };
    // TODO: update with actual available size
    size_t availableToRead() { return mWorkBuffer.capacity(); };
    size_t availableToWrite() { return mWorkBuffer.capacity(); };

  private:
    std::shared_ptr<StatusMQ> mStatusMQ;
    std::shared_ptr<DataMQ> mInputMQ;
    std::shared_ptr<DataMQ> mOutputMQ;
    // TODO handle effect process input and output
    // work buffer set by effect instances, the access and update are in same thread
    std::vector<int8_t> mWorkBuffer;
};
}  // namespace aidl::android::hardware::audio::effect
+7 −8
Original line number Diff line number Diff line
@@ -22,11 +22,9 @@
#include <android-base/thread_annotations.h>
#include <system/thread_defs.h>

namespace aidl::android::hardware::audio::effect {

enum class RetCode { SUCCESS, ERROR };
#include "effect-impl/EffectTypes.h"

std::string toString(RetCode& code);
namespace aidl::android::hardware::audio::effect {

class EffectThread {
  public:
@@ -35,10 +33,11 @@ class EffectThread {
    virtual ~EffectThread();

    // called by effect implementation.
    RetCode create(const std::string& name, const int priority = ANDROID_PRIORITY_URGENT_AUDIO);
    RetCode destroy();
    RetCode start();
    RetCode stop();
    RetCode createThread(const std::string& name,
                         const int priority = ANDROID_PRIORITY_URGENT_AUDIO);
    RetCode destroyThread();
    RetCode startThread();
    RetCode stopThread();

    // Will call process() in a loop if the thread is running.
    void threadLoop();
Loading