From d23bbdf7e1099aed5fd6003167def61975e3d8a4 Mon Sep 17 00:00:00 2001 From: Iscle Date: Wed, 2 Apr 2025 01:32:17 +0200 Subject: [PATCH] Add effect boilerplate code --- src/ViPER4Android.cpp | 78 ----------- src/include/log/log.h | 2 + src/viper_aidl.cpp | 295 ++++++++++++++++++++++++++++++++++++++---- src/viper_aidl.h | 37 +++++- 4 files changed, 307 insertions(+), 105 deletions(-) create mode 100644 src/include/log/log.h diff --git a/src/ViPER4Android.cpp b/src/ViPER4Android.cpp index 839d31a..8423f1e 100644 --- a/src/ViPER4Android.cpp +++ b/src/ViPER4Android.cpp @@ -3,18 +3,6 @@ #include "essential.h" #include "viper/constants.h" #include "ViperContext.h" -#include "aidl/android/hardware/audio/effect/Descriptor.h" -#include "aidl/android/hardware/audio/effect/Flags.h" -#include "aidl/android/hardware/audio/effect/IEffect.h" -#include "aidl/android/media/audio/common/AudioUuid.h" -#include -#include -#include "viper_aidl.h" - -using aidl::android::hardware::audio::effect::Descriptor; -using aidl::android::hardware::audio::effect::Flags; -using aidl::android::hardware::audio::effect::IEffect; -using aidl::android::media::audio::common::AudioUuid; extern "C" { struct ViperHandle { @@ -92,72 +80,6 @@ static int32_t viperLibraryGetDescriptor(const effect_uuid_t *uuid, effect_descr } } // extern "C" -extern "C" binder_exception_t createEffect(const AudioUuid *audio_uuid, std::shared_ptr *instance) { - if (audio_uuid == nullptr || instance == nullptr) { - VIPER_LOGE("createEffect called with null arguments"); - return EX_ILLEGAL_ARGUMENT; - } - VIPER_LOGD("createEffect called"); - *instance = ndk::SharedRefBase::make(); - return EX_NONE; -} - -extern "C" binder_exception_t destroyEffect(const std::shared_ptr &instanceSp) { - VIPER_LOGD("destroyEffect called"); - return EX_ILLEGAL_STATE; -} - -inline AudioUuid stringToUuid(const char* str) { - AudioUuid uuid{}; - uint32_t tmp[10]; - if (!str || sscanf(str, "%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x", tmp, - tmp + 1, tmp + 2, tmp + 3, tmp + 4, tmp + 5, tmp + 6, - tmp + 7, tmp + 8, tmp + 9) < 10) { - return uuid; - } - - uuid.timeLow = (uint32_t)tmp[0]; - uuid.timeMid = (uint16_t)tmp[1]; - uuid.timeHiAndVersion = (uint16_t)tmp[2]; - uuid.clockSeq = (uint16_t)tmp[3]; - uuid.node.insert(uuid.node.end(), {(uint8_t)tmp[4], (uint8_t)tmp[5], (uint8_t)tmp[6], - (uint8_t)tmp[7], (uint8_t)tmp[8], (uint8_t)tmp[9]}); - return uuid; -} - -const AudioUuid kType = stringToUuid("b9bc100c-26cd-42e6-acb6-cad8c3f778de"); -const AudioUuid kUuid = stringToUuid("90380da3-8536-4744-a6a3-5731970e640f"); -const Descriptor kDescriptor = { - .common = { - .id = { - .type = kType, - .uuid = kUuid, - .proxy = std::nullopt - }, - .flags = { - .type = Flags::Type::INSERT, - .insert = Flags::Insert::LAST, - .volume = Flags::Volume::NONE - }, - .name = VIPER_NAME, - .implementor = VIPER_AUTHORS, - }, -}; - -extern "C" binder_exception_t queryEffect(const AudioUuid *audio_uuid, Descriptor *descriptor) { - if (audio_uuid == nullptr || descriptor == nullptr) { - VIPER_LOGE("queryEffect called with null arguments"); - return EX_ILLEGAL_ARGUMENT; - } - if (*audio_uuid != kUuid) { - VIPER_LOGE("queryEffect called with invalid uuid"); - return EX_ILLEGAL_ARGUMENT; - } - VIPER_LOGD("queryEffect: returning descriptor"); - *descriptor = kDescriptor; - return EX_NONE; -} - extern "C" __attribute__((visibility("default"))) audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM = { diff --git a/src/include/log/log.h b/src/include/log/log.h new file mode 100644 index 0000000..b61608c --- /dev/null +++ b/src/include/log/log.h @@ -0,0 +1,2 @@ +/* Shim for external dependencies */ +#include \ No newline at end of file diff --git a/src/viper_aidl.cpp b/src/viper_aidl.cpp index 818e218..3366919 100644 --- a/src/viper_aidl.cpp +++ b/src/viper_aidl.cpp @@ -1,49 +1,294 @@ #include "viper_aidl.h" #include "log.h" +#include "viper/constants.h" +#include +#include -::ndk::ScopedAStatus ViPER4AndroidAIDL::open(const aidl::android::hardware::audio::effect::Parameter::Common &in_common, - const std::optional<::aidl::android::hardware::audio::effect::Parameter::Specific> &in_specific, - ::aidl::android::hardware::audio::effect::IEffect::OpenEffectReturn *_aidl_return) { - VIPER_LOGD("open called"); - return ndk::ScopedAStatus(); +using aidl::android::hardware::audio::effect::CommandId; +using aidl::android::hardware::audio::effect::Descriptor; +using aidl::android::hardware::audio::effect::Flags; +using aidl::android::hardware::audio::effect::IEffect; +using aidl::android::hardware::audio::effect::Parameter; +using aidl::android::hardware::audio::effect::State; +using aidl::android::media::audio::common::AudioUuid; +using aidl::android::media::audio::common::PcmType; +using android::hardware::EventFlag; + +/** + * EventFlag to indicate that the data FMQ is not Empty after a write. + * TODO: b/277900230, Define in future AIDL version. + */ +static constexpr uint32_t kEventFlagDataMqNotEmpty = 0x1 << 11; + +inline AudioUuid stringToUuid(const char* str) { + AudioUuid uuid{}; + uint32_t tmp[10]; + if (!str || sscanf(str, "%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x", tmp, + tmp + 1, tmp + 2, tmp + 3, tmp + 4, tmp + 5, tmp + 6, + tmp + 7, tmp + 8, tmp + 9) < 10) { + return uuid; + } + + uuid.timeLow = (uint32_t)tmp[0]; + uuid.timeMid = (uint16_t)tmp[1]; + uuid.timeHiAndVersion = (uint16_t)tmp[2]; + uuid.clockSeq = (uint16_t)tmp[3]; + uuid.node.insert(uuid.node.end(), {(uint8_t)tmp[4], (uint8_t)tmp[5], (uint8_t)tmp[6], + (uint8_t)tmp[7], (uint8_t)tmp[8], (uint8_t)tmp[9]}); + return uuid; } -::ndk::ScopedAStatus ViPER4AndroidAIDL::close() { +const AudioUuid kType = stringToUuid("b9bc100c-26cd-42e6-acb6-cad8c3f778de"); +const AudioUuid kUuid = stringToUuid("90380da3-8536-4744-a6a3-5731970e640f"); +const Descriptor kDescriptor = { + .common = { + .id = { + .type = kType, + .uuid = kUuid, + .proxy = std::nullopt + }, + .flags = { + .type = Flags::Type::INSERT, + .insert = Flags::Insert::LAST, + .volume = Flags::Volume::NONE + }, + .name = VIPER_NAME, + .implementor = VIPER_AUTHORS, + }, +}; + +constexpr size_t getPcmSampleSizeInBytes(::aidl::android::media::audio::common::PcmType pcm) { + using ::aidl::android::media::audio::common::PcmType; + switch (pcm) { + case PcmType::UINT_8_BIT: + return 1; + case PcmType::INT_16_BIT: + return 2; + case PcmType::INT_32_BIT: + return 4; + case PcmType::FIXED_Q_8_24: + return 4; + case PcmType::FLOAT_32_BIT: + return 4; + case PcmType::INT_24_BIT: + return 3; + } + return 0; +} + +constexpr size_t getChannelCount( + const ::aidl::android::media::audio::common::AudioChannelLayout& layout, + int32_t mask = std::numeric_limits::max()) { + using Tag = ::aidl::android::media::audio::common::AudioChannelLayout::Tag; + switch (layout.getTag()) { + case Tag::none: + return 0; + case Tag::invalid: + return 0; + case Tag::indexMask: + return __builtin_popcount(layout.get() & mask); + case Tag::layoutMask: + return __builtin_popcount(layout.get() & mask); + case Tag::voiceMask: + return __builtin_popcount(layout.get() & mask); + } + return 0; +} + +constexpr size_t getFrameSizeInBytes( + const ::aidl::android::media::audio::common::AudioFormatDescription& format, + const ::aidl::android::media::audio::common::AudioChannelLayout& layout) { + if (format == ::aidl::android::media::audio::common::AudioFormatDescription{}) { + // Unspecified format. + return 0; + } + using ::aidl::android::media::audio::common::AudioFormatType; + if (format.type == AudioFormatType::PCM) { + return getPcmSampleSizeInBytes(format.pcm) * getChannelCount(layout); + } else if (format.type == AudioFormatType::NON_PCM) { + // For non-PCM formats always use the underlying PCM size. The default value for + // PCM is "UINT_8_BIT", thus non-encapsulated streams have the frame size of 1. + return getPcmSampleSizeInBytes(format.pcm); + } + // Something unexpected. + return 0; +} + +ndk::ScopedAStatus ViPER4AndroidAIDL::open(const Parameter::Common &common, + const std::optional &specific, + IEffect::OpenEffectReturn *ret) { + if (common.input.base.format.pcm != PcmType::FLOAT_32_BIT) { + VIPER_LOGE("open called with invalid PCM type"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + std::lock_guard lg(mMutex); + + if (mState != State::INIT) { + VIPER_LOGD("open: already opened"); + return ndk::ScopedAStatus::ok(); + } + + size_t mInputFrameSize = getFrameSizeInBytes( + common.input.base.format, common.input.base.channelMask); + size_t mOutputFrameSize = getFrameSizeInBytes( + common.output.base.format, common.output.base.channelMask); + + size_t inBufferSizeInFloat = common.input.frameCount * mInputFrameSize / sizeof(float); + size_t outBufferSizeInFloat = common.output.frameCount * mOutputFrameSize / sizeof(float); + + // only status FMQ use the EventFlag + mStatusMQ = std::make_shared(1, true /*configureEventFlagWord*/); + mInputMQ = std::make_shared(inBufferSizeInFloat); + mOutputMQ = std::make_shared(outBufferSizeInFloat); + + if (!mStatusMQ->isValid() || !mInputMQ->isValid() || !mOutputMQ->isValid()) { + VIPER_LOGE("open: failed to create message queues"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + + android::status_t status = EventFlag::createEventFlag( + mStatusMQ->getEventFlagWord(), &mEventFlag); + if (status != android::OK || mEventFlag == nullptr) { + VIPER_LOGE("open: failed to create event flag"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + + mWorkBuffer.resize(std::max(inBufferSizeInFloat, outBufferSizeInFloat)); + + if (specific.has_value()) { + VIPER_LOGD("open: specific parameters provided, ignoring for now..."); + } + + mState = State::IDLE; + + ret->statusMQ = mStatusMQ->dupeDesc(); + ret->inputDataMQ = mInputMQ->dupeDesc(); + ret->outputDataMQ = mOutputMQ->dupeDesc(); + + mThread = std::thread(&ViPER4AndroidAIDL::threadLoop, this); + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus ViPER4AndroidAIDL::close() { VIPER_LOGD("close called"); - return ndk::ScopedAStatus(); + return ndk::ScopedAStatus::ok(); } -::ndk::ScopedAStatus -ViPER4AndroidAIDL::getDescriptor(::aidl::android::hardware::audio::effect::Descriptor *_aidl_return) { - VIPER_LOGD("getDescriptor called"); - return ndk::ScopedAStatus(); +ndk::ScopedAStatus +ViPER4AndroidAIDL::getDescriptor(Descriptor *descriptor) { + if (descriptor == nullptr) { + VIPER_LOGE("getDescriptor called with null descriptor"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + VIPER_LOGD("getDescriptor: returning descriptor"); + *descriptor = kDescriptor; + return ndk::ScopedAStatus::ok(); } -::ndk::ScopedAStatus ViPER4AndroidAIDL::command(::aidl::android::hardware::audio::effect::CommandId in_commandId) { +ndk::ScopedAStatus ViPER4AndroidAIDL::command(CommandId command_id) { VIPER_LOGD("command called"); - return ndk::ScopedAStatus(); + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } -::ndk::ScopedAStatus ViPER4AndroidAIDL::getState(::aidl::android::hardware::audio::effect::State *_aidl_return) { +ndk::ScopedAStatus ViPER4AndroidAIDL::getState(State *state) { VIPER_LOGD("getState called"); - return ndk::ScopedAStatus(); + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } -::ndk::ScopedAStatus -ViPER4AndroidAIDL::setParameter(const aidl::android::hardware::audio::effect::Parameter &in_param) { +ndk::ScopedAStatus +ViPER4AndroidAIDL::setParameter(const Parameter ¶meter) { VIPER_LOGD("setParameter called"); - return ndk::ScopedAStatus(); + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } -::ndk::ScopedAStatus -ViPER4AndroidAIDL::getParameter(const aidl::android::hardware::audio::effect::Parameter::Id &in_paramId, - ::aidl::android::hardware::audio::effect::Parameter *_aidl_return) { +ndk::ScopedAStatus +ViPER4AndroidAIDL::getParameter(const Parameter::Id ¶meter_id, Parameter *parameter) { VIPER_LOGD("getParameter called"); - return ndk::ScopedAStatus(); + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } -::ndk::ScopedAStatus -ViPER4AndroidAIDL::reopen(::aidl::android::hardware::audio::effect::IEffect::OpenEffectReturn *_aidl_return) { +ndk::ScopedAStatus +ViPER4AndroidAIDL::reopen(IEffect::OpenEffectReturn *open_effect_return) { VIPER_LOGD("reopen called"); - return ndk::ScopedAStatus(); + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } + +void ViPER4AndroidAIDL::threadLoop() { + VIPER_LOGD("threadLoop started"); + while (true) { + { + std::unique_lock l(mThreadMutex); + ::android::base::ScopedLockAssertion lock_assertion(mThreadMutex); + mThreadCv.wait(l, [&]() REQUIRES(mThreadMutex) { return mThreadExit || !mThreadStop; }); + if (mThreadExit) { + VIPER_LOGD("threadLoop exiting"); + return; + } + } + process(); + } +} + +void ViPER4AndroidAIDL::process() { +/** + * wait for the EventFlag without lock, it's ok because the mEventFlag pointer will not change + * in the life cycle of workerThread (threadLoop). + */ + uint32_t efState = 0; + if (!mEventFlag || + mEventFlag->wait(kEventFlagDataMqNotEmpty, &efState, 0 /* no timeout */, true /* retry */) != android::OK || + !(efState & kEventFlagDataMqNotEmpty)) { + VIPER_LOGE("process: failed to wait for event flag"); + return; + } + + { + std::lock_guard lg(mMutex); + if (mState != State::PROCESSING && mState != State::DRAINING) { + VIPER_LOGD("process: skip process in state: %d", mState); + return; + } + + auto buffer = mWorkBuffer.data(); + auto processSamples = std::min(mInputMQ->availableToRead(), mOutputMQ->availableToWrite()); + if (processSamples) { + mInputMQ->read(buffer, processSamples); +// IEffect::Status status = effectProcessImpl(buffer, buffer, processSamples); + VIPER_LOGD("process: processing %zu samples", processSamples); + IEffect::Status status = {STATUS_OK, static_cast(processSamples), static_cast(processSamples)}; + mOutputMQ->write(buffer, status.fmqProduced); + mStatusMQ->writeBlocking(&status, 1); + } + } +} + +extern "C" binder_exception_t createEffect(const AudioUuid *audio_uuid, std::shared_ptr *instance) { + if (audio_uuid == nullptr || instance == nullptr) { + VIPER_LOGE("createEffect called with null arguments"); + return EX_ILLEGAL_ARGUMENT; + } + VIPER_LOGD("createEffect: creating effect instance"); + *instance = ndk::SharedRefBase::make(); + return EX_NONE; +} + +extern "C" binder_exception_t destroyEffect(const std::shared_ptr &instance) { + VIPER_LOGD("destroyEffect called"); + return EX_ILLEGAL_STATE; +} + +extern "C" binder_exception_t queryEffect(const AudioUuid *audio_uuid, Descriptor *descriptor) { + if (audio_uuid == nullptr || descriptor == nullptr) { + VIPER_LOGE("queryEffect called with null arguments"); + return EX_ILLEGAL_ARGUMENT; + } + if (*audio_uuid != kUuid) { + VIPER_LOGE("queryEffect called with invalid uuid"); + return EX_ILLEGAL_ARGUMENT; + } + VIPER_LOGD("queryEffect: returning descriptor"); + *descriptor = kDescriptor; + return EX_NONE; +} \ No newline at end of file diff --git a/src/viper_aidl.h b/src/viper_aidl.h index 877c580..313c84f 100644 --- a/src/viper_aidl.h +++ b/src/viper_aidl.h @@ -1,11 +1,19 @@ #pragma once -#include "aidl/android/hardware/audio/effect/BnEffect.h" +#include +#include +#include +#include +#include using aidl::android::hardware::audio::effect::BnEffect; +using aidl::android::hardware::audio::effect::State; class ViPER4AndroidAIDL : public BnEffect { - ::ndk::ScopedAStatus open(const ::aidl::android::hardware::audio::effect::Parameter::Common &in_common, const std::optional< ::aidl::android::hardware::audio::effect::Parameter::Specific> &in_specific, ::aidl::android::hardware::audio::effect::IEffect::OpenEffectReturn *_aidl_return) override; +public: + ViPER4AndroidAIDL(); + + ::ndk::ScopedAStatus open(const ::aidl::android::hardware::audio::effect::Parameter::Common &common, const std::optional< ::aidl::android::hardware::audio::effect::Parameter::Specific> &specific, ::aidl::android::hardware::audio::effect::IEffect::OpenEffectReturn *ret) override; ::ndk::ScopedAStatus close() override; ::ndk::ScopedAStatus getDescriptor(::aidl::android::hardware::audio::effect::Descriptor *_aidl_return) override; ::ndk::ScopedAStatus command(::aidl::android::hardware::audio::effect::CommandId in_commandId) override; @@ -13,4 +21,29 @@ class ViPER4AndroidAIDL : public BnEffect { ::ndk::ScopedAStatus setParameter(const ::aidl::android::hardware::audio::effect::Parameter &in_param) override; ::ndk::ScopedAStatus getParameter(const ::aidl::android::hardware::audio::effect::Parameter::Id &in_paramId, ::aidl::android::hardware::audio::effect::Parameter *_aidl_return) override; ::ndk::ScopedAStatus reopen(::aidl::android::hardware::audio::effect::IEffect::OpenEffectReturn *_aidl_return) override; +private: + typedef ::android::AidlMessageQueue< + IEffect::Status, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite> + StatusMQ; + typedef ::android::AidlMessageQueue< + float, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite> + DataMQ; + + void threadLoop(); + void process(); + + std::mutex mMutex; + State mState = State::INIT; + + std::shared_ptr mStatusMQ; + std::shared_ptr mInputMQ; + std::shared_ptr mOutputMQ; + android::hardware::EventFlag *mEventFlag; + std::vector mWorkBuffer; + + std::thread mThread; + std::mutex mThreadMutex; + std::condition_variable mThreadCv; + bool mThreadStop = true; + bool mThreadExit = false; }; \ No newline at end of file