diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp index 53ed908908..c48777f810 100644 --- a/audio/aidl/default/Android.bp +++ b/audio/aidl/default/Android.bp @@ -81,18 +81,23 @@ cc_defaults { cc_library_static { name: "libaudioeffectserviceexampleimpl", defaults: ["aidlaudioeffectservice_defaults"], - export_include_dirs: [ - "include", - "include/equalizer-impl/", - ], + export_include_dirs: ["include"], srcs: [ "EffectFactory.cpp", ], + header_libs: [ + "libsystem_headers", + ], visibility: [ ":__subpackages__", ], } +filegroup { + name: "effectCommonFile", + srcs: ["EffectThread.cpp"], +} + cc_binary { name: "android.hardware.audio.effect.service-aidl.example", relative_install_path: "hw", diff --git a/audio/aidl/default/EffectFactory.cpp b/audio/aidl/default/EffectFactory.cpp index a9848fd018..4877956ef7 100644 --- a/audio/aidl/default/EffectFactory.cpp +++ b/audio/aidl/default/EffectFactory.cpp @@ -16,57 +16,156 @@ #define LOG_TAG "AHAL_EffectFactory" #include +#include +#include "effect-impl/EffectUUID.h" #include "effectFactory-impl/EffectFactory.h" -#include "equalizer-impl/Equalizer.h" -#include "visualizer-impl/Visualizer.h" using aidl::android::media::audio::common::AudioUuid; namespace aidl::android::hardware::audio::effect { Factory::Factory() { - // TODO: implement this with xml parser on audio_effect.xml, and filter with optional - // parameters. + std::function dlClose = [](void* handle) -> void { + if (handle && dlclose(handle)) { + LOG(ERROR) << "dlclose failed " << dlerror(); + } + }; + // TODO: implement this with audio_effect.xml. + auto libHandle = + std::unique_ptr{dlopen("libequalizer.so", RTLD_LAZY), dlClose}; + if (!libHandle) { + LOG(ERROR) << __func__ << ": dlopen failed, err: " << dlerror(); + return; + } + + LOG(DEBUG) << __func__ << " dlopen uuid: " << EqualizerSwImplUUID.toString() << " handle " + << libHandle; + mEffectLibMap.insert({EqualizerSwImplUUID, std::make_pair(std::move(libHandle), nullptr)}); + Descriptor::Identity id; id.type = EqualizerTypeUUID; id.uuid = EqualizerSwImplUUID; mIdentityList.push_back(id); } -ndk::ScopedAStatus Factory::queryEffects(const std::optional& in_type, - const std::optional& in_instance, +Factory::~Factory() { + if (auto count = mEffectUuidMap.size()) { + LOG(ERROR) << __func__ << " remaining " << count + << " effect instances not destroyed indicating resource leak!"; + for (const auto& it : mEffectUuidMap) { + if (auto spEffect = it.first.lock()) { + LOG(ERROR) << __func__ << " erase remaining instance UUID " << it.second.toString(); + destroyEffectImpl(spEffect); + } + } + } +} + +ndk::ScopedAStatus Factory::queryEffects(const std::optional& in_type_uuid, + const std::optional& in_impl_uuid, std::vector* _aidl_return) { std::copy_if(mIdentityList.begin(), mIdentityList.end(), std::back_inserter(*_aidl_return), [&](auto& desc) { - return (!in_type.has_value() || in_type.value() == desc.type) && - (!in_instance.has_value() || in_instance.value() == desc.uuid); + return (!in_type_uuid.has_value() || in_type_uuid.value() == desc.type) && + (!in_impl_uuid.has_value() || in_impl_uuid.value() == desc.uuid); }); return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus Factory::createEffect( - const AudioUuid& in_impl_uuid, - std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>* _aidl_return) { +#define RETURN_IF_BINDER_EXCEPTION(functor) \ + { \ + binder_exception_t exception = functor; \ + if (EX_NONE != exception) { \ + LOG(ERROR) << #functor << ": failed with error " << exception; \ + return ndk::ScopedAStatus::fromExceptionCode(exception); \ + } \ + } + +ndk::ScopedAStatus Factory::createEffect(const AudioUuid& in_impl_uuid, + std::shared_ptr* _aidl_return) { LOG(DEBUG) << __func__ << ": UUID " << in_impl_uuid.toString(); if (in_impl_uuid == EqualizerSwImplUUID) { - *_aidl_return = ndk::SharedRefBase::make(); + if (mEffectLibMap.count(in_impl_uuid)) { + auto& lib = mEffectLibMap[in_impl_uuid]; + // didn't do dlsym yet + if (nullptr == lib.second) { + void* libHandle = lib.first.get(); + struct effect_interface_s intf = { + .createEffectFunc = (EffectCreateFunctor)dlsym(libHandle, "createEffect"), + .destroyEffectFunc = + (EffectDestroyFunctor)dlsym(libHandle, "destroyEffect")}; + auto dlInterface = std::make_unique(intf); + if (!dlInterface->createEffectFunc || !dlInterface->destroyEffectFunc) { + LOG(ERROR) << __func__ + << ": create or destroy symbol not exist in library: " << libHandle + << "!"; + return ndk::ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED); + } + lib.second = std::move(dlInterface); + } + + auto& libInterface = lib.second; + std::shared_ptr effectSp; + RETURN_IF_BINDER_EXCEPTION(libInterface->createEffectFunc(&effectSp)); + if (!effectSp) { + LOG(ERROR) << __func__ << ": library created null instance without return error!"; + return ndk::ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED); + } + *_aidl_return = effectSp; + mEffectUuidMap[std::weak_ptr(effectSp)] = in_impl_uuid; + LOG(DEBUG) << __func__ << ": instance " << effectSp.get() << " created successfully"; + return ndk::ScopedAStatus::ok(); + } else { + LOG(ERROR) << __func__ << ": library doesn't exist"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } } else { - LOG(ERROR) << __func__ << ": UUID " - << " not supported"; + LOG(ERROR) << __func__ << ": UUID not supported"; return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus Factory::destroyEffect( - const std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>& in_handle) { - if (in_handle) { - // TODO: b/245393900 need check the instance state with IEffect.getState before destroy. +ndk::ScopedAStatus Factory::destroyEffectImpl(const std::shared_ptr& in_handle) { + std::weak_ptr wpHandle(in_handle); + // find UUID with key (std::weak_ptr) + if (auto uuidIt = mEffectUuidMap.find(wpHandle); uuidIt != mEffectUuidMap.end()) { + auto& uuid = uuidIt->second; + // find implementation library with UUID + if (auto libIt = mEffectLibMap.find(uuid); libIt != mEffectLibMap.end()) { + if (libIt->second.second->destroyEffectFunc) { + RETURN_IF_BINDER_EXCEPTION(libIt->second.second->destroyEffectFunc(in_handle)); + } + } else { + LOG(ERROR) << __func__ << ": UUID " << uuid.toString() << " does not exist in libMap!"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + mEffectUuidMap.erase(uuidIt); return ndk::ScopedAStatus::ok(); } else { + LOG(ERROR) << __func__ << ": instance " << in_handle << " does not exist!"; return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } } +// go over the map and cleanup all expired weak_ptrs. +void Factory::cleanupEffectMap() { + for (auto it = mEffectUuidMap.begin(); it != mEffectUuidMap.end();) { + if (nullptr == it->first.lock()) { + it = mEffectUuidMap.erase(it); + } else { + ++it; + } + } +} + +ndk::ScopedAStatus Factory::destroyEffect(const std::shared_ptr& in_handle) { + LOG(DEBUG) << __func__ << ": instance " << in_handle.get(); + ndk::ScopedAStatus status = destroyEffectImpl(in_handle); + // always do the cleanup + cleanupEffectMap(); + return status; +} + } // namespace aidl::android::hardware::audio::effect diff --git a/audio/aidl/default/EffectThread.cpp b/audio/aidl/default/EffectThread.cpp new file mode 100644 index 0000000000..0ad9a146c6 --- /dev/null +++ b/audio/aidl/default/EffectThread.cpp @@ -0,0 +1,131 @@ +/* + * 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_EffectThread" +#include +#include +#include + +#include "effect-impl/EffectThread.h" + +namespace aidl::android::hardware::audio::effect { + +EffectThread::EffectThread() { + LOG(DEBUG) << __func__; +} + +EffectThread::~EffectThread() { + destroy(); + LOG(DEBUG) << __func__ << " done"; +}; + +RetCode EffectThread::create(const std::string& name, const int priority) { + if (mThread.joinable()) { + LOG(WARNING) << __func__ << " thread already created, no-op"; + return RetCode::SUCCESS; + } + mName = name; + mPriority = priority; + mThread = std::thread(&EffectThread::threadLoop, this); + LOG(DEBUG) << __func__ << " " << name << " priority " << mPriority << " done"; + return RetCode::SUCCESS; +} + +RetCode EffectThread::destroy() { + { + std::lock_guard lg(mMutex); + mStop = mExit = true; + } + mCv.notify_one(); + + if (mThread.joinable()) { + mThread.join(); + } + LOG(DEBUG) << __func__ << " done"; + return RetCode::SUCCESS; +} + +RetCode EffectThread::start() { + if (!mThread.joinable()) { + LOG(ERROR) << __func__ << " thread already destroyed"; + return RetCode::ERROR; + } + + { + std::lock_guard lg(mMutex); + if (!mStop) { + LOG(WARNING) << __func__ << " already start"; + return RetCode::SUCCESS; + } + mStop = false; + } + + mCv.notify_one(); + LOG(DEBUG) << __func__ << " done"; + return RetCode::SUCCESS; +} + +RetCode EffectThread::stop() { + if (!mThread.joinable()) { + LOG(ERROR) << __func__ << " thread already destroyed"; + return RetCode::ERROR; + } + + { + std::lock_guard lg(mMutex); + if (mStop) { + LOG(WARNING) << __func__ << " already stop"; + return RetCode::SUCCESS; + } + mStop = true; + } + LOG(DEBUG) << __func__ << " done"; + return RetCode::SUCCESS; +} + +void EffectThread::threadLoop() { + pthread_setname_np(pthread_self(), mName.substr(0, MAX_TASK_COMM_LEN - 1).c_str()); + setpriority(PRIO_PROCESS, 0, mPriority); + while (true) { + bool needExit = false; + { + std::unique_lock l(mMutex); + mCv.wait(l, [&]() REQUIRES(mMutex) { + needExit = mExit; + return mExit || !mStop; + }); + } + if (needExit) { + LOG(WARNING) << __func__ << " EXIT!"; + return; + } + // process without lock + process(); + } +} + +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 diff --git a/audio/aidl/default/equalizer/Android.bp b/audio/aidl/default/equalizer/Android.bp index b842149241..9c11347941 100644 --- a/audio/aidl/default/equalizer/Android.bp +++ b/audio/aidl/default/equalizer/Android.bp @@ -27,17 +27,28 @@ cc_library_shared { name: "libequalizer", vendor: true, shared_libs: [ + "libaudioaidlcommon", "libbase", - "libbinder_ndk", - "libstagefright_foundation", + "android.hardware.common-V2-ndk", ], defaults: [ + "aidlaudioservice_defaults", "latest_android_media_audio_common_types_ndk_shared", "latest_android_hardware_audio_effect_ndk_shared", ], - include_dirs: ["hardware/interfaces/audio/aidl/default/include/equalizer-impl"], + include_dirs: [ + "hardware/interfaces/audio/aidl/default/include", + "system/media/audio/include", + ], srcs: [ "Equalizer.cpp", + ":effectCommonFile", + ], + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + "-Wthread-safety", ], visibility: [ "//hardware/interfaces/audio/aidl/default", diff --git a/audio/aidl/default/equalizer/Equalizer.cpp b/audio/aidl/default/equalizer/Equalizer.cpp index 8b157faf0f..2e4e538b63 100644 --- a/audio/aidl/default/equalizer/Equalizer.cpp +++ b/audio/aidl/default/equalizer/Equalizer.cpp @@ -14,27 +14,243 @@ * limitations under the License. */ -#define LOG_TAG "AHAL_Equalizer" +#define LOG_TAG "AHAL_EqualizerSw" +#include #include +#include -#include "Equalizer.h" +#include "effect-impl/EffectUUID.h" +#include "equalizer-impl/EqualizerSw.h" + +using android::hardware::audio::common::getFrameSizeInBytes; namespace aidl::android::hardware::audio::effect { -ndk::ScopedAStatus Equalizer::open() { +extern "C" binder_exception_t createEffect(std::shared_ptr* instanceSpp) { + if (instanceSpp) { + *instanceSpp = ndk::SharedRefBase::make(); + 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 destroyEffect(const std::shared_ptr& instanceSp) { + State state; + ndk::ScopedAStatus status = instanceSp->getState(&state); + if (!status.isOk() || State::INIT != state) { + LOG(ERROR) << __func__ << " instance " << instanceSp.get() + << " in state: " << toString(state) << ", status: " << status.getDescription(); + return EX_ILLEGAL_STATE; + } + LOG(DEBUG) << __func__ << " instance " << instanceSp.get() << " destroyed"; + return EX_NONE; +} + +ndk::ScopedAStatus EqualizerSw::open(const Parameter::Common& common, + const Parameter::Specific& specific, + OpenEffectReturn* _aidl_return) { + LOG(DEBUG) << __func__; + if (mState != State::INIT) { + LOG(WARNING) << __func__ << " eq already open"; + return ndk::ScopedAStatus::ok(); + } + + // Set essential parameters before create worker thread. + setCommonParameter(common); + setSpecificParameter(specific); + + LOG(DEBUG) << " common: " << common.toString() << " specific " << specific.toString(); + + auto& input = common.input; + 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)) { + return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_UNSUPPORTED_OPERATION, + "FailedToCreateFmq"); + } + + // create the worker thread + if (RetCode::SUCCESS != mWorker->create(LOG_TAG)) { + LOG(ERROR) << __func__ << " created worker thread failed"; + destroyFmq(); + return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_UNSUPPORTED_OPERATION, + "FailedToCreateFmq"); + } + + mState = State::IDLE; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus EqualizerSw::close() { + if (mState == State::INIT) { + LOG(WARNING) << __func__ << " instance already closed"; + return ndk::ScopedAStatus::ok(); + } else if (mState == State::PROCESSING) { + LOG(ERROR) << __func__ << " instance still processing"; + return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_STATE, + "EqInstanceProcessing"); + } + + // stop the worker thread + mState = State::INIT; + mWorker->destroy(); + destroyFmq(); LOG(DEBUG) << __func__; return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus Equalizer::close() { - LOG(DEBUG) << __func__; - return ndk::ScopedAStatus::ok(); -} - -ndk::ScopedAStatus Equalizer::getDescriptor(Descriptor* _aidl_return) { - LOG(DEBUG) << __func__ << "descriptor " << mDesc.toString(); +ndk::ScopedAStatus EqualizerSw::getDescriptor(Descriptor* _aidl_return) { + LOG(DEBUG) << __func__ << mDesc.toString(); *_aidl_return = mDesc; return ndk::ScopedAStatus::ok(); } +ndk::ScopedAStatus EqualizerSw::command(CommandId in_commandId) { + LOG(DEBUG) << __func__ << ": receive command:" << toString(in_commandId); + if (mState == State::INIT) { + LOG(ERROR) << __func__ << ": instance not open yet"; + return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_STATE, + "CommandStateError"); + } + switch (in_commandId) { + case CommandId::START: + // start processing. + mState = State::PROCESSING; + mWorker->start(); + LOG(DEBUG) << __func__ << " state: " << toString(mState); + return ndk::ScopedAStatus::ok(); + case CommandId::STOP: + // stop processing. + mState = State::IDLE; + mWorker->stop(); + LOG(DEBUG) << __func__ << " state: " << toString(mState); + return ndk::ScopedAStatus::ok(); + case CommandId::RESET: + // TODO: reset buffer status. + mState = State::IDLE; + mWorker->stop(); + LOG(DEBUG) << __func__ << " state: " << toString(mState); + return ndk::ScopedAStatus::ok(); + default: + return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT, + "CommandIdNotSupported"); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus EqualizerSw::setParameter(const Parameter& in_param) { + if (mState == State::INIT) { + LOG(ERROR) << __func__ << ": instance not open yet"; + return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_STATE, "StateError"); + } + LOG(DEBUG) << __func__ << " with: " << in_param.toString(); + auto tag = in_param.getTag(); + switch (tag) { + case Parameter::common: { + return setCommonParameter(in_param.get()); + } + case Parameter::specific: { + return setSpecificParameter(in_param.get()); + } + default: + return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT, + "ParameterNotSupported"); + } +} + +ndk::ScopedAStatus EqualizerSw::getParameter(const Parameter::Id& in_paramId, + Parameter* _aidl_return) { + LOG(DEBUG) << __func__ << in_paramId.toString(); + auto tag = in_paramId.getTag(); + switch (tag) { + case Parameter::Id::commonTag: { + _aidl_return->set(mCommonParam); + LOG(DEBUG) << __func__ << " get: " << _aidl_return->toString(); + return ndk::ScopedAStatus::ok(); + } + case Parameter::Id::specificTag: { + auto& id = in_paramId.get(); + if (id != Parameter::Specific::equalizer) { + LOG(ERROR) << " unsupported parameter Id: " << in_paramId.toString(); + return ndk::ScopedAStatus::fromExceptionCodeWithMessage( + EX_ILLEGAL_ARGUMENT, "Parameter::IdNotSupported"); + } + Parameter::Specific specific; + specific.set(mEqualizerParam); + _aidl_return->set(specific); + LOG(DEBUG) << __func__ << _aidl_return->toString(); + return ndk::ScopedAStatus::ok(); + } + default: + return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT, + "Parameter::IdNotSupported"); + } +} + +ndk::ScopedAStatus EqualizerSw::getState(State* _aidl_return) { + *_aidl_return = mState; + return ndk::ScopedAStatus::ok(); +} + +/// Private methods. +bool EqualizerSw::createFmq(int statusDepth, int inBufferSize, int outBufferSize, + OpenEffectReturn* ret) { + mStatusMQ = std::make_unique(statusDepth, true /*configureEventFlagWord*/); + mInputMQ = std::make_unique(inBufferSize); + mOutputMQ = std::make_unique(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(); + return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT, + "EffectNotSupported"); + } + + mEqualizerParam = specific.get(); + LOG(DEBUG) << __func__ << mEqualizerParam.toString(); + return ndk::ScopedAStatus::ok(); +} + +void EqualizerSw::cleanUp() { + if (State::PROCESSING == mState) { + command(CommandId::STOP); + } + if (State::INIT != mState) { + close(); + } +} + +// Processing method running in worker thread. +void EqualizerSwWorker::process() { + // TODO: add EQ processing with FMQ, should wait until data available before data processing. +} + } // namespace aidl::android::hardware::audio::effect diff --git a/audio/aidl/default/include/effect-impl/EffectThread.h b/audio/aidl/default/include/effect-impl/EffectThread.h new file mode 100644 index 0000000000..e831cea4b3 --- /dev/null +++ b/audio/aidl/default/include/effect-impl/EffectThread.h @@ -0,0 +1,59 @@ +/* + * 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 +#include +#include + +#include +#include + +namespace aidl::android::hardware::audio::effect { + +enum class RetCode { SUCCESS, ERROR }; + +std::string toString(RetCode& code); + +class EffectThread { + public: + // default priority is same as HIDL: ANDROID_PRIORITY_URGENT_AUDIO + 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(); + + // Will call process() in a loop if the thread is running. + void threadLoop(); + + // User of EffectThread must implement the effect processing logic in this method. + virtual void process() = 0; + const int MAX_TASK_COMM_LEN = 15; + + private: + std::mutex mMutex; + std::condition_variable mCv; + bool mExit GUARDED_BY(mMutex) = false; + bool mStop GUARDED_BY(mMutex) = true; + std::thread mThread; + int mPriority; + std::string mName; +}; +} // namespace aidl::android::hardware::audio::effect diff --git a/audio/aidl/default/include/effect-impl/EffectUUID.h b/audio/aidl/default/include/effect-impl/EffectUUID.h new file mode 100644 index 0000000000..99f6c24d83 --- /dev/null +++ b/audio/aidl/default/include/effect-impl/EffectUUID.h @@ -0,0 +1,45 @@ +/* + * 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 + +namespace aidl::android::hardware::audio::effect { + +using ::aidl::android::media::audio::common::AudioUuid; + +// Equalizer type UUID. +static const AudioUuid EqualizerTypeUUID = {static_cast(0x0bed4300), + 0xddd6, + 0x11db, + 0x8f34, + {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}}; + +// Equalizer implementation UUID. +static const AudioUuid EqualizerSwImplUUID = {static_cast(0x0bed4300), + 0x847d, + 0x11df, + 0xbb17, + {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}}; + +// Visualizer type UUID. +static const AudioUuid VisualizerTypeUUID = {static_cast(0x1d4033c0), + 0x8557, + 0x11df, + 0x9f2d, + {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}}; + +} // namespace aidl::android::hardware::audio::effect diff --git a/audio/aidl/default/include/effectFactory-impl/EffectFactory.h b/audio/aidl/default/include/effectFactory-impl/EffectFactory.h index 8da55258cf..d3730489bb 100644 --- a/audio/aidl/default/include/effectFactory-impl/EffectFactory.h +++ b/audio/aidl/default/include/effectFactory-impl/EffectFactory.h @@ -16,6 +16,8 @@ #pragma once +#include +#include #include #include @@ -63,7 +65,26 @@ class Factory : public BnFactory { override; private: + ~Factory(); // List of effect descriptors supported by the devices. std::vector mIdentityList; + + typedef binder_exception_t (*EffectCreateFunctor)(std::shared_ptr*); + typedef binder_exception_t (*EffectDestroyFunctor)(const std::shared_ptr&); + struct effect_interface_s { + EffectCreateFunctor createEffectFunc; + EffectDestroyFunctor destroyEffectFunc; + }; + + std::map> /* dlHandle */, + std::unique_ptr>> + mEffectLibMap; + std::map, aidl::android::media::audio::common::AudioUuid, + std::owner_less<>> + mEffectUuidMap; + + ndk::ScopedAStatus destroyEffectImpl(const std::shared_ptr& in_handle); + void cleanupEffectMap(); }; } // namespace aidl::android::hardware::audio::effect diff --git a/audio/aidl/default/include/equalizer-impl/Equalizer.h b/audio/aidl/default/include/equalizer-impl/Equalizer.h deleted file mode 100644 index ea16cb9fac..0000000000 --- a/audio/aidl/default/include/equalizer-impl/Equalizer.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 -#include - -namespace aidl::android::hardware::audio::effect { - -// Equalizer type UUID. -static const ::aidl::android::media::audio::common::AudioUuid EqualizerTypeUUID = { - static_cast(0x0bed4300), - 0xddd6, - 0x11db, - 0x8f34, - {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}}; - -// Equalizer implementation UUID. -static const ::aidl::android::media::audio::common::AudioUuid EqualizerSwImplUUID = { - static_cast(0x0bed4300), - 0x847d, - 0x11df, - 0xbb17, - {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}}; - -class Equalizer : public BnEffect { - public: - Equalizer() = default; - ndk::ScopedAStatus open() override; - ndk::ScopedAStatus close() override; - ndk::ScopedAStatus getDescriptor(Descriptor* _aidl_return) override; - - private: - // Effect descriptor. - Descriptor mDesc = {.common = {.id = {.type = EqualizerTypeUUID, .uuid = EqualizerSwImplUUID}}}; -}; -} // namespace aidl::android::hardware::audio::effect diff --git a/audio/aidl/default/include/equalizer-impl/EqualizerSw.h b/audio/aidl/default/include/equalizer-impl/EqualizerSw.h new file mode 100644 index 0000000000..58ad1dee1d --- /dev/null +++ b/audio/aidl/default/include/equalizer-impl/EqualizerSw.h @@ -0,0 +1,86 @@ +/* + * 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 +#include +#include +#include + +#include "effect-impl/EffectThread.h" + +namespace aidl::android::hardware::audio::effect { + +class EqualizerSwWorker : public EffectThread { + // EqualizerSwWorker(const std::string name){EffectThread(name)}; + void process() override; +}; + +class EqualizerSw : public BnEffect { + public: + EqualizerSw() { + // create the worker + mWorker = std::make_unique(); + LOG(DEBUG) << __func__; + }; + ~EqualizerSw() { + cleanUp(); + LOG(DEBUG) << __func__; + }; + ndk::ScopedAStatus open(const Parameter::Common& common, const Parameter::Specific& specific, + OpenEffectReturn* _aidl_return) override; + ndk::ScopedAStatus close() override; + ndk::ScopedAStatus getDescriptor(Descriptor* _aidl_return) override; + + ndk::ScopedAStatus getState(State* _aidl_return) override; + ndk::ScopedAStatus command(CommandId in_commandId) override; + ndk::ScopedAStatus setParameter(const Parameter& in_param) override; + ndk::ScopedAStatus getParameter(const Parameter::Id& in_paramId, + Parameter* _aidl_return) override; + + private: + // effect processing thread. + std::unique_ptr mWorker; + // Effect descriptor. + const Descriptor mDesc = { + .common = {.id = {.type = EqualizerTypeUUID, .uuid = EqualizerSwImplUUID}}}; + + // Parameters. + Parameter::Common mCommonParam; + Equalizer mEqualizerParam; // TODO: the equalizer parameter needs to update + + // Instance state INIT by default. + State mState = State::INIT; + + typedef ::android::AidlMessageQueue< + Status, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite> + StatusMQ; + typedef ::android::AidlMessageQueue< + int8_t, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite> + DataMQ; + + std::unique_ptr mStatusMQ; + std::unique_ptr mInputMQ; + std::unique_ptr mOutputMQ; + + ndk::ScopedAStatus setCommonParameter(const Parameter::Common& common_param); + ndk::ScopedAStatus setSpecificParameter(const Parameter::Specific& specific); + bool createFmq(int statusDepth, int inBufferSize, int outBufferSize, OpenEffectReturn* ret); + void destroyFmq(); + void cleanUp(); +}; +} // namespace aidl::android::hardware::audio::effect diff --git a/audio/aidl/default/include/visualizer-impl/Visualizer.h b/audio/aidl/default/include/visualizer-impl/Visualizer.h deleted file mode 100644 index 4b82dd0911..0000000000 --- a/audio/aidl/default/include/visualizer-impl/Visualizer.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 - -namespace aidl::android::hardware::audio::effect { - -// Visualizer implementation UUID. -static const ::aidl::android::media::audio::common::AudioUuid VisualizerUUID = { - static_cast(0x1d4033c0), - 0x8557, - 0x11df, - 0x9f2d, - {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}}; - -} // namespace aidl::android::hardware::audio::effect \ No newline at end of file diff --git a/audio/aidl/vts/Android.bp b/audio/aidl/vts/Android.bp index 6ea7cef0e2..63fc4158b5 100644 --- a/audio/aidl/vts/Android.bp +++ b/audio/aidl/vts/Android.bp @@ -42,6 +42,36 @@ cc_test { ], } +cc_test { + name: "VtsHalAudioEffectFactoryTargetTest", + defaults: [ + "latest_android_media_audio_common_types_ndk_static", + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: [ + "VtsHalAudioEffectFactoryTargetTest.cpp", + ], + shared_libs: [ + "libbinder_ndk", + ], + static_libs: [ + "android.hardware.audio.effect-V1-ndk", + "android.hardware.common-V2-ndk", + "android.hardware.common.fmq-V1-ndk", + ], + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + "-Wthread-safety", + ], + test_suites: [ + "general-tests", + "vts", + ], +} + cc_test { name: "VtsHalAudioEffectTargetTest", defaults: [ @@ -57,6 +87,8 @@ cc_test { ], static_libs: [ "android.hardware.audio.effect-V1-ndk", + "android.hardware.common-V2-ndk", + "android.hardware.common.fmq-V1-ndk", ], cflags: [ "-Wall", diff --git a/audio/aidl/vts/EffectFactoryHelper.h b/audio/aidl/vts/EffectFactoryHelper.h new file mode 100644 index 0000000000..3cbca45eb4 --- /dev/null +++ b/audio/aidl/vts/EffectFactoryHelper.h @@ -0,0 +1,132 @@ +/* + * 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 +#include +#include +#include + +#include + +#include "TestUtils.h" + +using namespace android; + +using aidl::android::hardware::audio::effect::Descriptor; +using aidl::android::hardware::audio::effect::IEffect; +using aidl::android::hardware::audio::effect::IFactory; +using aidl::android::hardware::audio::effect::Parameter; +using aidl::android::media::audio::common::AudioUuid; + +class EffectFactoryHelper { + public: + explicit EffectFactoryHelper(const std::string& name) : mServiceName(name) {} + + void ConnectToFactoryService() { + mEffectFactory = IFactory::fromBinder(binderUtil.connectToService(mServiceName)); + ASSERT_NE(mEffectFactory, nullptr); + } + + void RestartFactoryService() { + ASSERT_NE(mEffectFactory, nullptr); + mEffectFactory = IFactory::fromBinder(binderUtil.restartService()); + ASSERT_NE(mEffectFactory, nullptr); + ClearEffectMap(); + } + + void QueryEffects(const std::optional& in_type, + const std::optional& in_instance, + std::vector* _aidl_return) { + ASSERT_NE(mEffectFactory, nullptr); + EXPECT_IS_OK(mEffectFactory->queryEffects(in_type, in_instance, _aidl_return)); + mIds = *_aidl_return; + } + + void CreateEffects() { + ASSERT_NE(mEffectFactory, nullptr); + for (const auto& id : mIds) { + std::shared_ptr effect; + EXPECT_IS_OK(mEffectFactory->createEffect(id.uuid, &effect)); + EXPECT_NE(effect, nullptr) << id.toString(); + if (effect) { + mEffectIdMap[effect] = id; + } + } + } + + void CreateEffectsAndExpect( + const std::vector>& uuid_status) { + ASSERT_NE(mEffectFactory, nullptr); + for (const auto& it : uuid_status) { + std::shared_ptr effect; + auto status = mEffectFactory->createEffect(it.first.uuid, &effect); + EXPECT_STATUS(it.second, status); + if (effect) { + mEffectIdMap[effect] = it.first; + } + } + } + + void DestroyEffectAndExpect(std::shared_ptr& instance, binder_exception_t exception) { + ASSERT_NE(mEffectFactory, nullptr); + auto status = mEffectFactory->destroyEffect(instance); + EXPECT_STATUS(exception, status); + } + + void QueryAndCreateAllEffects() { + ASSERT_NE(mEffectFactory, nullptr); + EXPECT_IS_OK(mEffectFactory->queryEffects(std::nullopt, std::nullopt, &mCompleteIds)); + for (const auto& id : mCompleteIds) { + std::shared_ptr effect; + EXPECT_IS_OK(mEffectFactory->createEffect(id.uuid, &effect)); + EXPECT_NE(effect, nullptr) << id.toString(); + mEffectIdMap[effect] = id; + } + } + + void DestroyEffects(const binder_exception_t expected = EX_NONE, const int remaining = 0) { + ASSERT_NE(mEffectFactory, nullptr); + + for (auto it = mEffectIdMap.begin(); it != mEffectIdMap.end();) { + auto erased = it++; + auto status = mEffectFactory->destroyEffect(erased->first); + EXPECT_STATUS(expected, status); + if (status.isOk()) { + mEffectIdMap.erase(erased); + } + } + EXPECT_EQ((unsigned int)remaining, mEffectIdMap.size()); + } + + std::shared_ptr GetFactory() { return mEffectFactory; } + const std::vector& GetEffectIds() { return mIds; } + const std::vector& GetCompleteEffectIdList() { return mCompleteIds; } + const std::map, Descriptor::Identity>& GetEffectMap() { + return mEffectIdMap; + } + void ClearEffectMap() { mEffectIdMap.clear(); } + + private: + std::shared_ptr mEffectFactory; + std::string mServiceName; + AudioHalBinderServiceUtil binderUtil; + std::vector mIds; + std::vector mCompleteIds; + + std::map, Descriptor::Identity> mEffectIdMap; +}; diff --git a/audio/aidl/vts/VtsHalAudioEffectFactoryTargetTest.cpp b/audio/aidl/vts/VtsHalAudioEffectFactoryTargetTest.cpp new file mode 100644 index 0000000000..dd17a6fc0d --- /dev/null +++ b/audio/aidl/vts/VtsHalAudioEffectFactoryTargetTest.cpp @@ -0,0 +1,235 @@ +/* + * 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. + */ + +#include +#include +#include + +#define LOG_TAG "VtsHalAudioEffectFactory" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "AudioHalBinderServiceUtil.h" +#include "EffectFactoryHelper.h" +#include "TestUtils.h" + +using namespace android; + +using aidl::android::hardware::audio::effect::Descriptor; +using aidl::android::hardware::audio::effect::IFactory; +using aidl::android::media::audio::common::AudioUuid; + +/// Effect factory testing. +class EffectFactoryTest : public testing::TestWithParam { + public: + void SetUp() override { ASSERT_NO_FATAL_FAILURE(mFactory.ConnectToFactoryService()); } + + void TearDown() override { mFactory.DestroyEffects(); } + + EffectFactoryHelper mFactory = EffectFactoryHelper(GetParam()); + + // TODO: these UUID can get from config file + // ec7178ec-e5e1-4432-a3f4-4657e6795210 + const AudioUuid nullUuid = {static_cast(0xec7178ec), + 0xe5e1, + 0x4432, + 0xa3f4, + {0x46, 0x57, 0xe6, 0x79, 0x52, 0x10}}; + const AudioUuid zeroUuid = { + static_cast(0x0), 0x0, 0x0, 0x0, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; + const Descriptor::Identity nullDesc = {.uuid = nullUuid}; + const Descriptor::Identity zeroDesc = {.uuid = zeroUuid}; +}; + +TEST_P(EffectFactoryTest, SetupAndTearDown) { + // Intentionally empty test body. +} + +TEST_P(EffectFactoryTest, CanBeRestarted) { + ASSERT_NO_FATAL_FAILURE(mFactory.RestartFactoryService()); +} + +TEST_P(EffectFactoryTest, QueriedDescriptorList) { + std::vector descriptors; + mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors); + EXPECT_NE(descriptors.size(), 0UL); +} + +TEST_P(EffectFactoryTest, DescriptorUUIDNotNull) { + std::vector descriptors; + mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors); + // TODO: Factory eventually need to return the full list of MUST supported AOSP effects. + for (auto& desc : descriptors) { + EXPECT_NE(desc.type, zeroUuid); + EXPECT_NE(desc.uuid, zeroUuid); + } +} + +TEST_P(EffectFactoryTest, QueriedDescriptorNotExistType) { + std::vector descriptors; + mFactory.QueryEffects(nullUuid, std::nullopt, &descriptors); + EXPECT_EQ(descriptors.size(), 0UL); +} + +TEST_P(EffectFactoryTest, QueriedDescriptorNotExistInstance) { + std::vector descriptors; + mFactory.QueryEffects(std::nullopt, nullUuid, &descriptors); + EXPECT_EQ(descriptors.size(), 0UL); +} + +TEST_P(EffectFactoryTest, CreateAndDestroyOnce) { + std::vector descriptors; + mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors); + auto numIds = mFactory.GetEffectIds().size(); + EXPECT_NE(numIds, 0UL); + + auto& effectMap = mFactory.GetEffectMap(); + EXPECT_EQ(effectMap.size(), 0UL); + mFactory.CreateEffects(); + EXPECT_EQ(effectMap.size(), numIds); + mFactory.DestroyEffects(); + EXPECT_EQ(effectMap.size(), 0UL); +} + +TEST_P(EffectFactoryTest, CreateAndDestroyRepeat) { + std::vector descriptors; + mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors); + auto numIds = mFactory.GetEffectIds().size(); + EXPECT_NE(numIds, 0UL); + + auto& effectMap = mFactory.GetEffectMap(); + EXPECT_EQ(effectMap.size(), 0UL); + mFactory.CreateEffects(); + EXPECT_EQ(effectMap.size(), numIds); + mFactory.DestroyEffects(); + EXPECT_EQ(effectMap.size(), 0UL); + + // Create and destroy again + mFactory.CreateEffects(); + EXPECT_EQ(effectMap.size(), numIds); + mFactory.DestroyEffects(); + EXPECT_EQ(effectMap.size(), 0UL); +} + +TEST_P(EffectFactoryTest, CreateMultipleInstanceOfSameEffect) { + std::vector descriptors; + mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors); + auto numIds = mFactory.GetEffectIds().size(); + EXPECT_NE(numIds, 0UL); + + auto& effectMap = mFactory.GetEffectMap(); + EXPECT_EQ(effectMap.size(), 0UL); + mFactory.CreateEffects(); + EXPECT_EQ(effectMap.size(), numIds); + // Create effect instances of same implementation + mFactory.CreateEffects(); + EXPECT_EQ(effectMap.size(), 2 * numIds); + + mFactory.CreateEffects(); + EXPECT_EQ(effectMap.size(), 3 * numIds); + + mFactory.DestroyEffects(); + EXPECT_EQ(effectMap.size(), 0UL); +} + +// Expect EX_ILLEGAL_ARGUMENT when create with invalid UUID. +TEST_P(EffectFactoryTest, CreateWithInvalidUuid) { + std::vector> descriptors; + descriptors.push_back(std::make_pair(nullDesc, EX_ILLEGAL_ARGUMENT)); + descriptors.push_back(std::make_pair(zeroDesc, EX_ILLEGAL_ARGUMENT)); + + auto& effectMap = mFactory.GetEffectMap(); + mFactory.CreateEffectsAndExpect(descriptors); + EXPECT_EQ(effectMap.size(), 0UL); +} + +// Expect EX_ILLEGAL_ARGUMENT when destroy null interface. +TEST_P(EffectFactoryTest, DestroyWithInvalidInterface) { + std::shared_ptr spDummyEffect(nullptr); + + mFactory.DestroyEffectAndExpect(spDummyEffect, EX_ILLEGAL_ARGUMENT); +} + +TEST_P(EffectFactoryTest, CreateAndRemoveReference) { + std::vector descriptors; + mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors); + auto numIds = mFactory.GetEffectIds().size(); + EXPECT_NE(numIds, 0UL); + + auto& effectMap = mFactory.GetEffectMap(); + EXPECT_EQ(effectMap.size(), 0UL); + mFactory.CreateEffects(); + EXPECT_EQ(effectMap.size(), numIds); + // remove all reference + mFactory.ClearEffectMap(); + EXPECT_EQ(effectMap.size(), 0UL); +} + +TEST_P(EffectFactoryTest, CreateRemoveReferenceAndCreateDestroy) { + std::vector descriptors; + mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors); + auto numIds = mFactory.GetEffectIds().size(); + EXPECT_NE(numIds, 0UL); + + auto& effectMap = mFactory.GetEffectMap(); + EXPECT_EQ(effectMap.size(), 0UL); + mFactory.CreateEffects(); + EXPECT_EQ(effectMap.size(), numIds); + // remove all reference + mFactory.ClearEffectMap(); + EXPECT_EQ(effectMap.size(), 0UL); + + // Create and destroy again + mFactory.CreateEffects(); + EXPECT_EQ(effectMap.size(), numIds); + mFactory.DestroyEffects(); + EXPECT_EQ(effectMap.size(), 0UL); +} + +TEST_P(EffectFactoryTest, CreateRestartAndCreateDestroy) { + std::vector descriptors; + mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors); + auto numIds = mFactory.GetEffectIds().size(); + auto& effectMap = mFactory.GetEffectMap(); + mFactory.CreateEffects(); + EXPECT_EQ(effectMap.size(), numIds); + ASSERT_NO_FATAL_FAILURE(mFactory.RestartFactoryService()); + + mFactory.CreateEffects(); + EXPECT_EQ(effectMap.size(), numIds); + mFactory.DestroyEffects(); + EXPECT_EQ(effectMap.size(), 0UL); +} + +INSTANTIATE_TEST_SUITE_P(EffectFactoryTest, EffectFactoryTest, + testing::ValuesIn(android::getAidlHalInstanceNames(IFactory::descriptor)), + android::PrintInstanceNameToString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EffectFactoryTest); + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ABinderProcess_setThreadPoolMaxThreadCount(1); + ABinderProcess_startThreadPool(); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp b/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp index 8b5eb13cdb..23b20bd1fb 100644 --- a/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp +++ b/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp @@ -30,205 +30,68 @@ #include #include +#include #include +#include +#include #include "AudioHalBinderServiceUtil.h" +#include "EffectFactoryHelper.h" #include "TestUtils.h" using namespace android; using ndk::ScopedAStatus; +using aidl::android::hardware::audio::effect::CommandId; using aidl::android::hardware::audio::effect::Descriptor; using aidl::android::hardware::audio::effect::IEffect; using aidl::android::hardware::audio::effect::IFactory; -using aidl::android::media::audio::common::AudioUuid; +using aidl::android::hardware::audio::effect::Parameter; +using aidl::android::hardware::audio::effect::State; +using aidl::android::media::audio::common::AudioChannelLayout; +using aidl::android::media::audio::common::AudioDeviceType; -class EffectFactoryHelper { - public: - explicit EffectFactoryHelper(const std::string& name) : mServiceName(name) {} - - void ConnectToFactoryService() { - mEffectFactory = IFactory::fromBinder(binderUtil.connectToService(mServiceName)); - ASSERT_NE(mEffectFactory, nullptr); - } - - void RestartFactoryService() { - ASSERT_NE(mEffectFactory, nullptr); - mEffectFactory = IFactory::fromBinder(binderUtil.restartService()); - ASSERT_NE(mEffectFactory, nullptr); - } - - void QueryAllEffects() { - EXPECT_NE(mEffectFactory, nullptr); - EXPECT_IS_OK(mEffectFactory->queryEffects(std::nullopt, std::nullopt, &mCompleteIds)); - } - - void QueryEffects(const std::optional& in_type, - const std::optional& in_instance, - std::vector* _aidl_return) { - EXPECT_NE(mEffectFactory, nullptr); - EXPECT_IS_OK(mEffectFactory->queryEffects(in_type, in_instance, _aidl_return)); - mIds = *_aidl_return; - } - - void CreateEffects() { - EXPECT_NE(mEffectFactory, nullptr); - for (const auto& id : mIds) { - std::shared_ptr effect; - EXPECT_IS_OK(mEffectFactory->createEffect(id.uuid, &effect)); - EXPECT_NE(effect, nullptr) << id.toString(); - mEffectIdMap[effect] = id; - } - } - - void DestroyEffects() { - EXPECT_NE(mEffectFactory, nullptr); - for (const auto& it : mEffectIdMap) { - EXPECT_IS_OK(mEffectFactory->destroyEffect(it.first)); - } - mEffectIdMap.clear(); - } - - std::shared_ptr GetFactory() { return mEffectFactory; } - const std::vector& GetEffectIds() { return mIds; } - const std::vector& GetCompleteEffectIdList() { return mCompleteIds; } - const std::unordered_map, Descriptor::Identity>& GetEffectMap() { - return mEffectIdMap; - } - - private: - std::shared_ptr mEffectFactory; - std::string mServiceName; - AudioHalBinderServiceUtil binderUtil; - std::vector mIds; - std::vector mCompleteIds; - std::unordered_map, Descriptor::Identity> mEffectIdMap; -}; - -/// Effect factory testing. -class EffectFactoryTest : public testing::TestWithParam { - public: - void SetUp() override { ASSERT_NO_FATAL_FAILURE(mFactory.ConnectToFactoryService()); } - - void TearDown() override { mFactory.DestroyEffects(); } - - EffectFactoryHelper mFactory = EffectFactoryHelper(GetParam()); - - // TODO: these UUID can get from config file - // ec7178ec-e5e1-4432-a3f4-4657e6795210 - const AudioUuid nullUuid = {static_cast(0xec7178ec), - 0xe5e1, - 0x4432, - 0xa3f4, - {0x46, 0x57, 0xe6, 0x79, 0x52, 0x10}}; - const AudioUuid zeroUuid = { - static_cast(0x0), 0x0, 0x0, 0x0, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; -}; - -TEST_P(EffectFactoryTest, SetupAndTearDown) { - // Intentionally empty test body. -} - -TEST_P(EffectFactoryTest, CanBeRestarted) { - ASSERT_NO_FATAL_FAILURE(mFactory.RestartFactoryService()); -} - -TEST_P(EffectFactoryTest, QueriedDescriptorList) { - std::vector descriptors; - mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors); - EXPECT_NE(descriptors.size(), 0UL); -} - -TEST_P(EffectFactoryTest, DescriptorUUIDNotNull) { - std::vector descriptors; - mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors); - // TODO: Factory eventually need to return the full list of MUST supported AOSP effects. - for (auto& desc : descriptors) { - EXPECT_NE(desc.type, zeroUuid); - EXPECT_NE(desc.uuid, zeroUuid); - } -} - -TEST_P(EffectFactoryTest, QueriedDescriptorNotExistType) { - std::vector descriptors; - mFactory.QueryEffects(nullUuid, std::nullopt, &descriptors); - EXPECT_EQ(descriptors.size(), 0UL); -} - -TEST_P(EffectFactoryTest, QueriedDescriptorNotExistInstance) { - std::vector descriptors; - mFactory.QueryEffects(std::nullopt, nullUuid, &descriptors); - EXPECT_EQ(descriptors.size(), 0UL); -} - -TEST_P(EffectFactoryTest, CreateAndDestroyRepeat) { - std::vector descriptors; - mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors); - auto numIds = mFactory.GetEffectIds().size(); - EXPECT_NE(numIds, 0UL); - - EXPECT_EQ(mFactory.GetEffectMap().size(), 0UL); - mFactory.CreateEffects(); - EXPECT_EQ(mFactory.GetEffectMap().size(), numIds); - mFactory.DestroyEffects(); - EXPECT_EQ(mFactory.GetEffectMap().size(), 0UL); - - // Create and destroy again - mFactory.CreateEffects(); - EXPECT_EQ(mFactory.GetEffectMap().size(), numIds); - mFactory.DestroyEffects(); - EXPECT_EQ(mFactory.GetEffectMap().size(), 0UL); -} - -TEST_P(EffectFactoryTest, CreateMultipleInstanceOfSameEffect) { - std::vector descriptors; - mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors); - auto numIds = mFactory.GetEffectIds().size(); - EXPECT_NE(numIds, 0UL); - - EXPECT_EQ(mFactory.GetEffectMap().size(), 0UL); - mFactory.CreateEffects(); - EXPECT_EQ(mFactory.GetEffectMap().size(), numIds); - // Create effect instances of same implementation - mFactory.CreateEffects(); - EXPECT_EQ(mFactory.GetEffectMap().size(), 2 * numIds); - - mFactory.CreateEffects(); - EXPECT_EQ(mFactory.GetEffectMap().size(), 3 * numIds); - - mFactory.DestroyEffects(); - EXPECT_EQ(mFactory.GetEffectMap().size(), 0UL); -} - -INSTANTIATE_TEST_SUITE_P(EffectFactoryTest, EffectFactoryTest, - testing::ValuesIn(android::getAidlHalInstanceNames(IFactory::descriptor)), - android::PrintInstanceNameToString); -GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EffectFactoryTest); - -/// Effect testing. class AudioEffect : public testing::TestWithParam { public: void SetUp() override { - ASSERT_NO_FATAL_FAILURE(mFactory.ConnectToFactoryService()); - ASSERT_NO_FATAL_FAILURE(mFactory.CreateEffects()); + ASSERT_NO_FATAL_FAILURE(mFactoryHelper.ConnectToFactoryService()); + CreateEffects(); + initParamCommon(); + initParamSpecific(); } void TearDown() override { CloseEffects(); - ASSERT_NO_FATAL_FAILURE(mFactory.DestroyEffects()); + DestroyEffects(); } void OpenEffects() { - auto open = [](const std::shared_ptr& effect) { EXPECT_IS_OK(effect->open()); }; + auto open = [&](const std::shared_ptr& effect) { + IEffect::OpenEffectReturn ret; + EXPECT_IS_OK(effect->open(mCommon, mSpecific, &ret)); + }; EXPECT_NO_FATAL_FAILURE(ForEachEffect(open)); } - void CloseEffects() { - auto close = [](const std::shared_ptr& effect) { EXPECT_IS_OK(effect->close()); }; + void CloseEffects(const binder_status_t status = EX_NONE) { + auto close = [&](const std::shared_ptr& effect) { + EXPECT_STATUS(status, effect->close()); + }; + EXPECT_NO_FATAL_FAILURE(ForEachEffect(close)); } + void CreateEffects(const int n = 1) { + for (int i = 0; i < n; i++) { + ASSERT_NO_FATAL_FAILURE(mFactoryHelper.QueryAndCreateAllEffects()); + } + } + + void DestroyEffects(const binder_status_t status = EX_NONE, const int remaining = 0) { + ASSERT_NO_FATAL_FAILURE(mFactoryHelper.DestroyEffects(status, remaining)); + } + void GetEffectDescriptors() { auto get = [](const std::shared_ptr& effect) { Descriptor desc; @@ -237,16 +100,101 @@ class AudioEffect : public testing::TestWithParam { EXPECT_NO_FATAL_FAILURE(ForEachEffect(get)); } + void CommandEffects(CommandId command) { + auto close = [&](const std::shared_ptr& effect) { + EXPECT_IS_OK(effect->command(command)); + }; + EXPECT_NO_FATAL_FAILURE(ForEachEffect(close)); + } + + void CommandEffectsExpectStatus(CommandId command, const binder_status_t status) { + auto func = [&](const std::shared_ptr& effect) { + EXPECT_STATUS(status, effect->command(command)); + }; + EXPECT_NO_FATAL_FAILURE(ForEachEffect(func)); + } + + void ExpectState(State expected) { + auto get = [&](const std::shared_ptr& effect) { + State state = State::INIT; + EXPECT_IS_OK(effect->getState(&state)); + EXPECT_EQ(expected, state); + }; + EXPECT_NO_FATAL_FAILURE(ForEachEffect(get)); + } + + void SetParameter() { + auto func = [&](const std::shared_ptr& effect) { + Parameter param; + param.set(mCommon); + EXPECT_IS_OK(effect->setParameter(param)); + }; + EXPECT_NO_FATAL_FAILURE(ForEachEffect(func)); + } + + void VerifyParameters() { + auto func = [&](const std::shared_ptr& effect) { + Parameter paramCommonGet = Parameter(), paramCommonExpect = Parameter(); + Parameter::Id id; + id.set(0); + paramCommonExpect.set(mCommon); + EXPECT_IS_OK(effect->getParameter(id, ¶mCommonGet)); + EXPECT_EQ(paramCommonExpect, paramCommonGet) + << paramCommonExpect.toString() << " vs " << paramCommonGet.toString(); + }; + EXPECT_NO_FATAL_FAILURE(ForEachEffect(func)); + } + template void ForEachEffect(Functor functor) { - auto effectMap = mFactory.GetEffectMap(); + auto effectMap = mFactoryHelper.GetEffectMap(); for (const auto& it : effectMap) { SCOPED_TRACE(it.second.toString()); functor(it.first); } } - EffectFactoryHelper mFactory = EffectFactoryHelper(GetParam()); + void initParamCommon(int session = -1, int ioHandle = -1, + AudioDeviceType deviceType = AudioDeviceType::NONE, + int iSampleRate = 48000, int oSampleRate = 48000, long iFrameCount = 0x100, + long oFrameCount = 0x100) { + mCommon.session = session; + mCommon.ioHandle = ioHandle; + mCommon.device.type = deviceType; + mCommon.input.base.sampleRate = iSampleRate; + mCommon.input.base.channelMask = mInputChannelLayout; + mCommon.input.frameCount = iFrameCount; + mCommon.output.base.sampleRate = oSampleRate; + mCommon.output.base.channelMask = mOutputChannelLayout; + mCommon.output.frameCount = oFrameCount; + } + + void initParamSpecific(Parameter::Specific::Tag tag = Parameter::Specific::equalizer) { + switch (tag) { + case Parameter::Specific::equalizer: + mSpecific.set(); + break; + default: + return; + } + } + + void setInputChannelLayout(AudioChannelLayout input) { mInputChannelLayout = input; } + void setOutputChannelLayout(AudioChannelLayout output) { mOutputChannelLayout = output; } + + EffectFactoryHelper mFactoryHelper = EffectFactoryHelper(GetParam()); + + private: + AudioChannelLayout mInputChannelLayout = + AudioChannelLayout::make( + AudioChannelLayout::LAYOUT_STEREO); + AudioChannelLayout mOutputChannelLayout = + AudioChannelLayout::make( + AudioChannelLayout::LAYOUT_STEREO); + + Parameter::Common mCommon; + Parameter::Specific mSpecific; + static IEffect::OpenEffectReturn mOpenReturn; }; TEST_P(AudioEffect, OpenEffectTest) { @@ -286,7 +234,7 @@ TEST_P(AudioEffect, DescriptorIdExistAndUnique) { Descriptor desc; std::vector idList; EXPECT_IS_OK(effect->getDescriptor(&desc)); - mFactory.QueryEffects(desc.common.id.type, desc.common.id.uuid, &idList); + mFactoryHelper.QueryEffects(desc.common.id.type, desc.common.id.uuid, &idList); EXPECT_EQ(idList.size(), 1UL); }; EXPECT_NO_FATAL_FAILURE(ForEachEffect(checker)); @@ -295,7 +243,7 @@ TEST_P(AudioEffect, DescriptorIdExistAndUnique) { auto stringHash = [](const Descriptor::Identity& id) { return std::hash()(id.toString()); }; - auto vec = mFactory.GetCompleteEffectIdList(); + auto vec = mFactoryHelper.GetCompleteEffectIdList(); std::unordered_set idSet(0, stringHash); for (auto it : vec) { EXPECT_EQ(idSet.count(it), 0UL); @@ -303,6 +251,212 @@ TEST_P(AudioEffect, DescriptorIdExistAndUnique) { } } +/// State testing. +// An effect instance is in INIT state by default after it was created. +TEST_P(AudioEffect, InitStateAfterCreation) { + ExpectState(State::INIT); +} + +// An effect instance transfer to INIT state after it was open successfully with IEffect.open(). +TEST_P(AudioEffect, IdleStateAfterOpen) { + EXPECT_NO_FATAL_FAILURE(OpenEffects()); + ExpectState(State::IDLE); + EXPECT_NO_FATAL_FAILURE(CloseEffects()); +} + +// An effect instance is in PROCESSING state after it receive an START command. +TEST_P(AudioEffect, ProcessingStateAfterStart) { + EXPECT_NO_FATAL_FAILURE(OpenEffects()); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::START)); + ExpectState(State::PROCESSING); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::STOP)); + EXPECT_NO_FATAL_FAILURE(CloseEffects()); +} + +// An effect instance transfer to IDLE state after Command.Id.STOP in PROCESSING state. +TEST_P(AudioEffect, IdleStateAfterStop) { + EXPECT_NO_FATAL_FAILURE(OpenEffects()); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::START)); + ExpectState(State::PROCESSING); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::STOP)); + ExpectState(State::IDLE); + EXPECT_NO_FATAL_FAILURE(CloseEffects()); +} + +// An effect instance transfer to IDLE state after Command.Id.RESET in PROCESSING state. +TEST_P(AudioEffect, IdleStateAfterReset) { + EXPECT_NO_FATAL_FAILURE(OpenEffects()); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::START)); + ExpectState(State::PROCESSING); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::RESET)); + ExpectState(State::IDLE); + EXPECT_NO_FATAL_FAILURE(CloseEffects()); +} + +// An effect instance transfer to INIT if instance receive a close() call. +TEST_P(AudioEffect, InitStateAfterClose) { + EXPECT_NO_FATAL_FAILURE(OpenEffects()); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::START)); + ExpectState(State::PROCESSING); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::STOP)); + ExpectState(State::IDLE); + EXPECT_NO_FATAL_FAILURE(CloseEffects()); + ExpectState(State::INIT); +} + +// An effect instance shouldn't accept any command before open. +TEST_P(AudioEffect, NoCommandAcceptedBeforeOpen) { + ExpectState(State::INIT); + EXPECT_NO_FATAL_FAILURE(CommandEffectsExpectStatus(CommandId::START, EX_ILLEGAL_STATE)); + EXPECT_NO_FATAL_FAILURE(CommandEffectsExpectStatus(CommandId::STOP, EX_ILLEGAL_STATE)); + EXPECT_NO_FATAL_FAILURE(CommandEffectsExpectStatus(CommandId::RESET, EX_ILLEGAL_STATE)); + ExpectState(State::INIT); +} + +// No-op when receive STOP command in IDLE state. +TEST_P(AudioEffect, StopCommandInIdleStateNoOp) { + ExpectState(State::INIT); + EXPECT_NO_FATAL_FAILURE(OpenEffects()); + ExpectState(State::IDLE); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::STOP)); + ExpectState(State::IDLE); + EXPECT_NO_FATAL_FAILURE(CloseEffects()); +} + +// No-op when receive STOP command in IDLE state. +TEST_P(AudioEffect, ResetCommandInIdleStateNoOp) { + ExpectState(State::INIT); + EXPECT_NO_FATAL_FAILURE(OpenEffects()); + ExpectState(State::IDLE); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::RESET)); + ExpectState(State::IDLE); + EXPECT_NO_FATAL_FAILURE(CloseEffects()); +} + +// Repeat START and STOP command. +TEST_P(AudioEffect, RepeatStartAndStop) { + EXPECT_NO_FATAL_FAILURE(OpenEffects()); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::START)); + ExpectState(State::PROCESSING); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::STOP)); + ExpectState(State::IDLE); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::START)); + ExpectState(State::PROCESSING); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::STOP)); + ExpectState(State::IDLE); + EXPECT_NO_FATAL_FAILURE(CloseEffects()); +} + +// Repeat START and RESET command. +TEST_P(AudioEffect, RepeatStartAndReset) { + EXPECT_NO_FATAL_FAILURE(OpenEffects()); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::START)); + ExpectState(State::PROCESSING); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::RESET)); + ExpectState(State::IDLE); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::START)); + ExpectState(State::PROCESSING); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::RESET)); + ExpectState(State::IDLE); + EXPECT_NO_FATAL_FAILURE(CloseEffects()); +} + +// Repeat START and STOP command, try to close at PROCESSING state. +TEST_P(AudioEffect, CloseProcessingStateEffects) { + EXPECT_NO_FATAL_FAILURE(OpenEffects()); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::START)); + ExpectState(State::PROCESSING); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::STOP)); + ExpectState(State::IDLE); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::START)); + ExpectState(State::PROCESSING); + EXPECT_NO_FATAL_FAILURE(CloseEffects(EX_ILLEGAL_STATE)); + // cleanup + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::STOP)); + ExpectState(State::IDLE); +} + +// Expect EX_ILLEGAL_STATE if the effect instance is not in a proper state to be destroyed. +TEST_P(AudioEffect, DestroyOpenEffects) { + // cleanup all effects. + EXPECT_NO_FATAL_FAILURE(CloseEffects()); + ASSERT_NO_FATAL_FAILURE(DestroyEffects()); + + // open effects, destroy without close, expect to get EX_ILLEGAL_STATE status. + EXPECT_NO_FATAL_FAILURE(CreateEffects()); + EXPECT_NO_FATAL_FAILURE(OpenEffects()); + EXPECT_NO_FATAL_FAILURE(DestroyEffects(EX_ILLEGAL_STATE, 1)); + EXPECT_NO_FATAL_FAILURE(CloseEffects()); +} + +/// Parameter testing. +// Verify parameters pass in open can be successfully get. +TEST_P(AudioEffect, VerifyParametersAfterOpen) { + EXPECT_NO_FATAL_FAILURE(OpenEffects()); + EXPECT_NO_FATAL_FAILURE(VerifyParameters()); + EXPECT_NO_FATAL_FAILURE(CloseEffects()); +} + +// Verify parameters pass in set can be successfully get. +TEST_P(AudioEffect, SetAndGetParameter) { + EXPECT_NO_FATAL_FAILURE(OpenEffects()); + EXPECT_NO_FATAL_FAILURE(VerifyParameters()); + initParamCommon(1 /* session */, 1 /* ioHandle */, AudioDeviceType::IN_DEFAULT /* deviceType */, + 44100 /* iSampleRate */, 44100 /* oSampleRate */); + EXPECT_NO_FATAL_FAILURE(SetParameter()); + EXPECT_NO_FATAL_FAILURE(VerifyParameters()); + EXPECT_NO_FATAL_FAILURE(CloseEffects()); +} + +// Verify parameters pass in set can be successfully get. +TEST_P(AudioEffect, SetAndGetParameterInProcessing) { + EXPECT_NO_FATAL_FAILURE(OpenEffects()); + EXPECT_NO_FATAL_FAILURE(VerifyParameters()); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::START)); + ExpectState(State::PROCESSING); + initParamCommon(1 /* session */, 1 /* ioHandle */, AudioDeviceType::IN_DEFAULT /* deviceType */, + 44100 /* iSampleRate */, 44100 /* oSampleRate */); + EXPECT_NO_FATAL_FAILURE(SetParameter()); + EXPECT_NO_FATAL_FAILURE(VerifyParameters()); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::STOP)); + ExpectState(State::IDLE); + EXPECT_NO_FATAL_FAILURE(CloseEffects()); +} + +// Parameters kept after reset. +TEST_P(AudioEffect, ResetAndVerifyParameter) { + EXPECT_NO_FATAL_FAILURE(OpenEffects()); + EXPECT_NO_FATAL_FAILURE(VerifyParameters()); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::START)); + ExpectState(State::PROCESSING); + initParamCommon(1 /* session */, 1 /* ioHandle */, AudioDeviceType::IN_DEFAULT /* deviceType */, + 44100 /* iSampleRate */, 44100 /* oSampleRate */); + EXPECT_NO_FATAL_FAILURE(SetParameter()); + EXPECT_NO_FATAL_FAILURE(VerifyParameters()); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::RESET)); + ExpectState(State::IDLE); + EXPECT_NO_FATAL_FAILURE(VerifyParameters()); + EXPECT_NO_FATAL_FAILURE(CloseEffects()); +} + +// Multiple instances of same implementation running. +TEST_P(AudioEffect, MultipleInstancesRunning) { + EXPECT_NO_FATAL_FAILURE(CreateEffects(3)); + ExpectState(State::INIT); + EXPECT_NO_FATAL_FAILURE(OpenEffects()); + ExpectState(State::IDLE); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::START)); + ExpectState(State::PROCESSING); + initParamCommon(1 /* session */, 1 /* ioHandle */, AudioDeviceType::IN_DEFAULT /* deviceType */, + 44100 /* iSampleRate */, 44100 /* oSampleRate */); + EXPECT_NO_FATAL_FAILURE(SetParameter()); + EXPECT_NO_FATAL_FAILURE(VerifyParameters()); + EXPECT_NO_FATAL_FAILURE(CommandEffects(CommandId::STOP)); + ExpectState(State::IDLE); + EXPECT_NO_FATAL_FAILURE(VerifyParameters()); + EXPECT_NO_FATAL_FAILURE(CloseEffects()); +} + INSTANTIATE_TEST_SUITE_P(AudioEffectTest, AudioEffect, testing::ValuesIn(android::getAidlHalInstanceNames(IFactory::descriptor)), android::PrintInstanceNameToString);