Merge "Haptic Generator : Add libeffect implementation" am: e1f6e9ce9e am: e7326c0b39
Original change: https://android-review.googlesource.com/c/platform/frameworks/av/+/2362124
Change-Id: Ic148d345ab4643373e2712d3bef1ecfb37cbbb85
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/media/libeffects/hapticgenerator/Android.bp b/media/libeffects/hapticgenerator/Android.bp
index ba511fe..02a94d1 100644
--- a/media/libeffects/hapticgenerator/Android.bp
+++ b/media/libeffects/hapticgenerator/Android.bp
@@ -57,3 +57,32 @@
"libaudioeffects",
],
}
+
+cc_library_shared {
+ name: "libhapticgeneratoraidl",
+ srcs: [
+ "aidl/EffectHapticGenerator.cpp",
+ "aidl/HapticGeneratorContext.cpp",
+ "Processors.cpp",
+ ":effectCommonFile",
+ ],
+ defaults: [
+ "aidlaudioservice_defaults",
+ "latest_android_hardware_audio_effect_ndk_shared",
+ "latest_android_media_audio_common_types_ndk_shared",
+ ],
+ header_libs: [
+ "libaudioeffects",
+ "libhardware_headers"
+ ],
+ shared_libs: [
+ "libbase",
+ "libaudioutils",
+ "libcutils",
+ "liblog",
+ "libvibratorutils",
+ ],
+ visibility: [
+ "//hardware/interfaces/audio/aidl/default",
+ ],
+}
diff --git a/media/libeffects/hapticgenerator/aidl/EffectHapticGenerator.cpp b/media/libeffects/hapticgenerator/aidl/EffectHapticGenerator.cpp
new file mode 100644
index 0000000..16fc230
--- /dev/null
+++ b/media/libeffects/hapticgenerator/aidl/EffectHapticGenerator.cpp
@@ -0,0 +1,189 @@
+/*
+ * 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_HapticGeneratorImpl"
+
+#include "EffectHapticGenerator.h"
+
+#include <android-base/logging.h>
+#include <audio_effects/effect_hapticgenerator.h>
+
+using aidl::android::hardware::audio::effect::Descriptor;
+using aidl::android::hardware::audio::effect::HapticGeneratorImpl;
+using aidl::android::hardware::audio::effect::IEffect;
+using aidl::android::hardware::audio::effect::kHapticGeneratorImplUUID;
+using aidl::android::media::audio::common::AudioUuid;
+
+extern "C" binder_exception_t createEffect(const AudioUuid* in_impl_uuid,
+ std::shared_ptr<IEffect>* instanceSpp) {
+ if (!in_impl_uuid || *in_impl_uuid != kHapticGeneratorImplUUID) {
+ LOG(ERROR) << __func__ << "uuid not supported";
+ return EX_ILLEGAL_ARGUMENT;
+ }
+ if (instanceSpp) {
+ *instanceSpp = ndk::SharedRefBase::make<HapticGeneratorImpl>();
+ LOG(DEBUG) << __func__ << " instance " << instanceSpp->get() << " created";
+ return EX_NONE;
+ } else {
+ LOG(ERROR) << __func__ << " invalid input parameter!";
+ return EX_ILLEGAL_ARGUMENT;
+ }
+}
+
+extern "C" binder_exception_t queryEffect(const AudioUuid* in_impl_uuid, Descriptor* _aidl_return) {
+ if (!in_impl_uuid || *in_impl_uuid != kHapticGeneratorImplUUID) {
+ LOG(ERROR) << __func__ << "uuid not supported";
+ return EX_ILLEGAL_ARGUMENT;
+ }
+ *_aidl_return = HapticGeneratorImpl::kDescriptor;
+ return EX_NONE;
+}
+
+namespace aidl::android::hardware::audio::effect {
+
+const std::string HapticGeneratorImpl::kEffectName = "Haptic Generator";
+const Descriptor HapticGeneratorImpl::kDescriptor = {
+ .common = {.id = {.type = kHapticGeneratorTypeUUID,
+ .uuid = kHapticGeneratorImplUUID,
+ .proxy = std::nullopt},
+ .flags = {.type = Flags::Type::INSERT, .insert = Flags::Insert::FIRST},
+ .name = HapticGeneratorImpl::kEffectName,
+ .implementor = "The Android Open Source Project"}};
+
+ndk::ScopedAStatus HapticGeneratorImpl::getDescriptor(Descriptor* _aidl_return) {
+ RETURN_IF(!_aidl_return, EX_ILLEGAL_ARGUMENT, "Parameter:nullptr");
+ LOG(DEBUG) << __func__ << kDescriptor.toString();
+ *_aidl_return = kDescriptor;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus HapticGeneratorImpl::commandImpl(CommandId command) {
+ RETURN_IF(!mContext, EX_NULL_POINTER, "nullContext");
+ switch (command) {
+ case CommandId::START:
+ mContext->enable();
+ break;
+ case CommandId::STOP:
+ mContext->disable();
+ break;
+ case CommandId::RESET:
+ mContext->reset();
+ break;
+ default:
+ LOG(ERROR) << __func__ << " commandId " << toString(command) << " not supported";
+ return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
+ "commandIdNotSupported");
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus HapticGeneratorImpl::setParameterSpecific(const Parameter::Specific& specific) {
+ RETURN_IF(Parameter::Specific::hapticGenerator != specific.getTag(), EX_ILLEGAL_ARGUMENT,
+ "EffectNotSupported");
+ RETURN_IF(!mContext, EX_NULL_POINTER, "nullContext");
+
+ auto& hgParam = specific.get<Parameter::Specific::hapticGenerator>();
+ auto tag = hgParam.getTag();
+
+ switch (tag) {
+ case HapticGenerator::hapticScale: {
+ RETURN_IF(mContext->setHgHapticScale(hgParam.get<HapticGenerator::hapticScale>()) !=
+ RetCode::SUCCESS,
+ EX_ILLEGAL_ARGUMENT, "setHapticScaleFailed");
+ return ndk::ScopedAStatus::ok();
+ }
+ case HapticGenerator::vibratorInfo: {
+ RETURN_IF(mContext->setHgVibratorInformation(
+ hgParam.get<HapticGenerator::vibratorInfo>()) != RetCode::SUCCESS,
+ EX_ILLEGAL_ARGUMENT, "setVibratorInfoFailed");
+ return ndk::ScopedAStatus::ok();
+ }
+ default: {
+ LOG(ERROR) << __func__ << " unsupported tag: " << toString(tag);
+ return ndk::ScopedAStatus::fromExceptionCodeWithMessage(
+ EX_ILLEGAL_ARGUMENT, "HapticGeneratorTagNotSupported");
+ }
+ }
+}
+
+ndk::ScopedAStatus HapticGeneratorImpl::getParameterSpecific(const Parameter::Id& id,
+ Parameter::Specific* specific) {
+ RETURN_IF(!specific, EX_NULL_POINTER, "nullPtr");
+ auto tag = id.getTag();
+ RETURN_IF(Parameter::Id::hapticGeneratorTag != tag, EX_ILLEGAL_ARGUMENT, "wrongIdTag");
+ auto hgId = id.get<Parameter::Id::hapticGeneratorTag>();
+ auto hgIdTag = hgId.getTag();
+ switch (hgIdTag) {
+ case HapticGenerator::Id::commonTag:
+ return getParameterHapticGenerator(hgId.get<HapticGenerator::Id::commonTag>(),
+ specific);
+ default:
+ LOG(ERROR) << __func__ << " unsupported tag: " << toString(hgIdTag);
+ return ndk::ScopedAStatus::fromExceptionCodeWithMessage(
+ EX_ILLEGAL_ARGUMENT, "HapticGeneratorTagNotSupported");
+ }
+}
+
+ndk::ScopedAStatus HapticGeneratorImpl::getParameterHapticGenerator(const HapticGenerator::Tag& tag,
+ Parameter::Specific* specific) {
+ RETURN_IF(!mContext, EX_NULL_POINTER, "nullContext");
+
+ HapticGenerator hgParam;
+ switch (tag) {
+ case HapticGenerator::hapticScale: {
+ hgParam.set<HapticGenerator::hapticScale>(mContext->getHgHapticScale());
+ break;
+ }
+ case HapticGenerator::vibratorInfo: {
+ hgParam.set<HapticGenerator::vibratorInfo>(mContext->getHgVibratorInformation());
+ break;
+ }
+ default: {
+ LOG(ERROR) << __func__ << " unsupported tag: " << toString(tag);
+ return ndk::ScopedAStatus::fromExceptionCodeWithMessage(
+ EX_ILLEGAL_ARGUMENT, "HapticGeneratorTagNotSupported");
+ }
+ }
+
+ specific->set<Parameter::Specific::hapticGenerator>(hgParam);
+ return ndk::ScopedAStatus::ok();
+}
+
+std::shared_ptr<EffectContext> HapticGeneratorImpl::createContext(const Parameter::Common& common) {
+ if (mContext) {
+ LOG(DEBUG) << __func__ << " context already exist";
+ return mContext;
+ }
+
+ mContext = std::make_shared<HapticGeneratorContext>(1 /* statusFmqDepth */, common);
+ return mContext;
+}
+
+RetCode HapticGeneratorImpl::releaseContext() {
+ if (mContext) {
+ mContext->reset();
+ }
+ return RetCode::SUCCESS;
+}
+
+// Processing method running in EffectWorker thread.
+IEffect::Status HapticGeneratorImpl::effectProcessImpl(float* in, float* out, int samples) {
+ IEffect::Status status = {EX_NULL_POINTER, 0, 0};
+ RETURN_VALUE_IF(!mContext, status, "nullContext");
+ return mContext->lvmProcess(in, out, samples);
+}
+
+} // namespace aidl::android::hardware::audio::effect
diff --git a/media/libeffects/hapticgenerator/aidl/EffectHapticGenerator.h b/media/libeffects/hapticgenerator/aidl/EffectHapticGenerator.h
new file mode 100644
index 0000000..02ca392
--- /dev/null
+++ b/media/libeffects/hapticgenerator/aidl/EffectHapticGenerator.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/audio/effect/BnEffect.h>
+
+#include "HapticGeneratorContext.h"
+#include "effect-impl/EffectImpl.h"
+#include "effect-impl/EffectUUID.h"
+
+namespace aidl::android::hardware::audio::effect {
+
+class HapticGeneratorImpl final : public EffectImpl {
+ public:
+ static const std::string kEffectName;
+ static const Descriptor kDescriptor;
+ HapticGeneratorImpl() { LOG(DEBUG) << __func__; }
+ ~HapticGeneratorImpl() {
+ cleanUp();
+ LOG(DEBUG) << __func__;
+ }
+
+ ndk::ScopedAStatus commandImpl(CommandId command) override;
+ ndk::ScopedAStatus getDescriptor(Descriptor* _aidl_return) override;
+ ndk::ScopedAStatus setParameterSpecific(const Parameter::Specific& specific) override;
+ ndk::ScopedAStatus getParameterSpecific(const Parameter::Id& id,
+ Parameter::Specific* specific) override;
+ IEffect::Status effectProcessImpl(float* in, float* out, int process) override;
+ std::shared_ptr<EffectContext> createContext(const Parameter::Common& common) override;
+ RetCode releaseContext() override;
+
+ std::shared_ptr<EffectContext> getContext() override { return mContext; }
+ std::string getEffectName() override { return kEffectName; }
+
+ private:
+ std::shared_ptr<HapticGeneratorContext> mContext;
+ ndk::ScopedAStatus getParameterHapticGenerator(const HapticGenerator::Tag& tag,
+ Parameter::Specific* specific);
+};
+
+} // namespace aidl::android::hardware::audio::effect
diff --git a/media/libeffects/hapticgenerator/aidl/HapticGeneratorContext.cpp b/media/libeffects/hapticgenerator/aidl/HapticGeneratorContext.cpp
new file mode 100644
index 0000000..5a32dc2
--- /dev/null
+++ b/media/libeffects/hapticgenerator/aidl/HapticGeneratorContext.cpp
@@ -0,0 +1,324 @@
+/*
+ * 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_HapticGeneratorContext"
+
+#include <Utils.h>
+#include <android-base/parsedouble.h>
+#include <android-base/properties.h>
+
+#include "HapticGeneratorContext.h"
+
+namespace aidl::android::hardware::audio::effect {
+
+HapticGeneratorContext::HapticGeneratorContext(int statusDepth, const Parameter::Common& common)
+ : EffectContext(statusDepth, common) {
+ LOG(DEBUG) << __func__;
+ mState = HAPTIC_GENERATOR_STATE_UNINITIALIZED;
+ mSampleRate = common.input.base.sampleRate;
+ mFrameCount = common.input.frameCount;
+ init_params(common.input.base.channelMask, common.output.base.channelMask);
+}
+
+HapticGeneratorContext::~HapticGeneratorContext() {
+ LOG(DEBUG) << __func__;
+ mState = HAPTIC_GENERATOR_STATE_UNINITIALIZED;
+}
+
+RetCode HapticGeneratorContext::enable() {
+ if (mState != HAPTIC_GENERATOR_STATE_INITIALIZED) {
+ return RetCode::ERROR_EFFECT_LIB_ERROR;
+ }
+ mState = HAPTIC_GENERATOR_STATE_ACTIVE;
+ return RetCode::SUCCESS;
+}
+
+RetCode HapticGeneratorContext::disable() {
+ if (mState != HAPTIC_GENERATOR_STATE_ACTIVE) {
+ return RetCode::ERROR_EFFECT_LIB_ERROR;
+ }
+ mState = HAPTIC_GENERATOR_STATE_INITIALIZED;
+ return RetCode::SUCCESS;
+}
+
+void HapticGeneratorContext::reset() {
+ for (auto& filter : mProcessorsRecord.filters) {
+ filter->clear();
+ }
+ for (auto& slowEnv : mProcessorsRecord.slowEnvs) {
+ slowEnv->clear();
+ }
+ for (auto& distortion : mProcessorsRecord.distortions) {
+ distortion->clear();
+ }
+}
+
+RetCode HapticGeneratorContext::setHgHapticScale(const HapticGenerator::HapticScale& hapticScale) {
+ mParams.mHapticScale = hapticScale;
+ if (hapticScale.scale == HapticGenerator::VibratorScale::MUTE) {
+ mParams.mHapticScales.erase(hapticScale.id);
+ } else {
+ mParams.mHapticScales.emplace(hapticScale.id, hapticScale.scale);
+ }
+ mParams.mMaxVibratorScale = hapticScale.scale;
+ for (const auto& [id, vibratorScale] : mParams.mHapticScales) {
+ mParams.mMaxVibratorScale = std::max(mParams.mMaxVibratorScale, vibratorScale);
+ }
+ return RetCode::SUCCESS;
+}
+
+RetCode HapticGeneratorContext::setHgVibratorInformation(
+ const HapticGenerator::VibratorInformation& vibratorInfo) {
+ mParams.mVibratorInfo = vibratorInfo;
+
+ if (mProcessorsRecord.bpf != nullptr) {
+ mProcessorsRecord.bpf->setCoefficients(::android::audio_effect::haptic_generator::bpfCoefs(
+ mParams.mVibratorInfo.resonantFrequencyHz, DEFAULT_BPF_Q, mSampleRate));
+ }
+ if (mProcessorsRecord.bsf != nullptr) {
+ mProcessorsRecord.bsf->setCoefficients(::android::audio_effect::haptic_generator::bsfCoefs(
+ mParams.mVibratorInfo.resonantFrequencyHz, mParams.mVibratorInfo.qFactor,
+ mParams.mVibratorInfo.qFactor / 2.0f, mSampleRate));
+ }
+ configure();
+ return RetCode::SUCCESS;
+}
+
+IEffect::Status HapticGeneratorContext::lvmProcess(float* in, float* out, int samples) {
+ LOG(DEBUG) << __func__ << " in " << in << " out " << out << " sample " << samples;
+
+ IEffect::Status status = {EX_NULL_POINTER, 0, 0};
+ RETURN_VALUE_IF(!in, status, "nullInput");
+ RETURN_VALUE_IF(!out, status, "nullOutput");
+ status = {EX_ILLEGAL_STATE, 0, 0};
+ RETURN_VALUE_IF(getInputFrameSize() != getOutputFrameSize(), status, "FrameSizeMismatch");
+ auto frameSize = getInputFrameSize();
+ RETURN_VALUE_IF(0 == frameSize, status, "zeroFrameSize");
+
+ LOG(DEBUG) << __func__ << " start processing";
+ // The audio data must not be modified but just written to
+ // output buffer according the access mode.
+ bool accumulate = false;
+ if (in != out) {
+ for (int i = 0; i < samples; i++) {
+ if (accumulate) {
+ out[i] += in[i];
+ } else {
+ out[i] = in[i];
+ }
+ }
+ }
+
+ if (mState != HAPTIC_GENERATOR_STATE_ACTIVE) {
+ return status;
+ }
+
+ if (mParams.mMaxVibratorScale == HapticGenerator::VibratorScale::MUTE) {
+ // Haptic channels are muted, not need to generate haptic data.
+ return {STATUS_OK, samples, samples};
+ }
+
+ // Resize buffer if the haptic sample count is greater than buffer size.
+ size_t hapticSampleCount = mFrameCount * mParams.mHapticChannelCount;
+ if (hapticSampleCount > mInputBuffer.size()) {
+ // The inputBuffer and outputBuffer must have the same size, which must be at least
+ // the haptic sample count.
+ mInputBuffer.resize(hapticSampleCount);
+ mOutputBuffer.resize(hapticSampleCount);
+ }
+
+ // Construct input buffer according to haptic channel source
+ for (size_t i = 0; i < mFrameCount; ++i) {
+ for (size_t j = 0; j < mParams.mHapticChannelCount; ++j) {
+ mInputBuffer[i * mParams.mHapticChannelCount + j] =
+ in[i * mParams.mAudioChannelCount + mParams.mHapticChannelSource[j]];
+ }
+ }
+
+ float* hapticOutBuffer =
+ runProcessingChain(mInputBuffer.data(), mOutputBuffer.data(), mFrameCount);
+ ::android::os::scaleHapticData(
+ hapticOutBuffer, hapticSampleCount,
+ static_cast<::android::os::HapticScale>(mParams.mMaxVibratorScale),
+ mParams.mVibratorInfo.qFactor);
+
+ // For haptic data, the haptic playback thread will copy the data from effect input
+ // buffer, which contains haptic data at the end of the buffer, directly to sink buffer.
+ // In that case, copy haptic data to input buffer instead of output buffer.
+ // Note: this may not work with rpc/binder calls
+ int offset = samples;
+ for (int i = 0; i < hapticSampleCount; ++i) {
+ in[samples + i] = hapticOutBuffer[i];
+ }
+ return {STATUS_OK, samples, static_cast<int32_t>(samples + hapticSampleCount)};
+}
+
+void HapticGeneratorContext::init_params(media::audio::common::AudioChannelLayout inputChMask,
+ media::audio::common::AudioChannelLayout outputChMask) {
+ mParams.mMaxVibratorScale = HapticGenerator::VibratorScale::MUTE;
+ mParams.mVibratorInfo.resonantFrequencyHz = DEFAULT_RESONANT_FREQUENCY;
+ mParams.mVibratorInfo.qFactor = DEFAULT_BSF_ZERO_Q;
+
+ mParams.mAudioChannelCount = ::android::hardware::audio::common::getChannelCount(
+ inputChMask, ~media::audio::common::AudioChannelLayout::LAYOUT_HAPTIC_AB);
+ mParams.mHapticChannelCount = ::android::hardware::audio::common::getChannelCount(
+ outputChMask, media::audio::common::AudioChannelLayout::LAYOUT_HAPTIC_AB);
+ LOG_ALWAYS_FATAL_IF(mParams.mHapticChannelCount > 2, "haptic channel count is too large");
+ for (size_t i = 0; i < mParams.mHapticChannelCount; ++i) {
+ // By default, use the first audio channel to generate haptic channels.
+ mParams.mHapticChannelSource[i] = 0;
+ }
+
+ mState = HAPTIC_GENERATOR_STATE_INITIALIZED;
+}
+
+float HapticGeneratorContext::getDistortionOutputGain() {
+ float distortionOutputGain = getFloatProperty(
+ "vendor.audio.hapticgenerator.distortion.output.gain", DEFAULT_DISTORTION_OUTPUT_GAIN);
+ LOG(DEBUG) << "Using distortion output gain as " << distortionOutputGain;
+ return distortionOutputGain;
+}
+
+float HapticGeneratorContext::getFloatProperty(const std::string& key, float defaultValue) {
+ float result;
+ std::string value = ::android::base::GetProperty(key, "");
+ if (!value.empty() && ::android::base::ParseFloat(value, &result)) {
+ return result;
+ }
+ return defaultValue;
+}
+
+void HapticGeneratorContext::addBiquadFilter(std::shared_ptr<HapticBiquadFilter> filter) {
+ // The process chain captures the shared pointer of the filter in lambda.
+ // The process record will keep a shared pointer to the filter so that it is possible to
+ // access the filter outside of the process chain.
+ mProcessorsRecord.filters.push_back(filter);
+ mProcessingChain.push_back([filter](float* out, const float* in, size_t frameCount) {
+ filter->process(out, in, frameCount);
+ });
+}
+
+/**
+ * Build haptic generator processing chain.
+ */
+void HapticGeneratorContext::buildProcessingChain() {
+ const size_t channelCount = mParams.mHapticChannelCount;
+ float highPassCornerFrequency = 50.0f;
+ auto hpf = ::android::audio_effect::haptic_generator::createHPF2(highPassCornerFrequency,
+ mSampleRate, channelCount);
+ addBiquadFilter(hpf);
+ float lowPassCornerFrequency = 9000.0f;
+ auto lpf = ::android::audio_effect::haptic_generator::createLPF2(lowPassCornerFrequency,
+ mSampleRate, channelCount);
+ addBiquadFilter(lpf);
+
+ auto ramp = std::make_shared<::android::audio_effect::haptic_generator::Ramp>(
+ channelCount); // ramp = half-wave rectifier.
+ // The process chain captures the shared pointer of the ramp in lambda. It will be the only
+ // reference to the ramp.
+ // The process record will keep a weak pointer to the ramp so that it is possible to access
+ // the ramp outside of the process chain.
+ mProcessorsRecord.ramps.push_back(ramp);
+ mProcessingChain.push_back([ramp](float* out, const float* in, size_t frameCount) {
+ ramp->process(out, in, frameCount);
+ });
+
+ highPassCornerFrequency = 60.0f;
+ hpf = ::android::audio_effect::haptic_generator::createHPF2(highPassCornerFrequency,
+ mSampleRate, channelCount);
+ addBiquadFilter(hpf);
+ lowPassCornerFrequency = 700.0f;
+ lpf = ::android::audio_effect::haptic_generator::createLPF2(lowPassCornerFrequency, mSampleRate,
+ channelCount);
+ addBiquadFilter(lpf);
+
+ lowPassCornerFrequency = 400.0f;
+ lpf = ::android::audio_effect::haptic_generator::createLPF2(lowPassCornerFrequency, mSampleRate,
+ channelCount);
+ addBiquadFilter(lpf);
+ lowPassCornerFrequency = 500.0f;
+ lpf = ::android::audio_effect::haptic_generator::createLPF2(lowPassCornerFrequency, mSampleRate,
+ channelCount);
+ addBiquadFilter(lpf);
+
+ auto bpf = ::android::audio_effect::haptic_generator::createBPF(
+ mParams.mVibratorInfo.resonantFrequencyHz, DEFAULT_BPF_Q, mSampleRate, channelCount);
+ mProcessorsRecord.bpf = bpf;
+ addBiquadFilter(bpf);
+
+ float normalizationPower = DEFAULT_SLOW_ENV_NORMALIZATION_POWER;
+ // The process chain captures the shared pointer of the slow envelope in lambda. It will
+ // be the only reference to the slow envelope.
+ // The process record will keep a weak pointer to the slow envelope so that it is possible
+ // to access the slow envelope outside of the process chain.
+ // SlowEnvelope = partial normalizer, or AGC.
+ auto slowEnv = std::make_shared<::android::audio_effect::haptic_generator::SlowEnvelope>(
+ 5.0f /*envCornerFrequency*/, mSampleRate, normalizationPower, 0.01f /*envOffset*/,
+ channelCount);
+ mProcessorsRecord.slowEnvs.push_back(slowEnv);
+ mProcessingChain.push_back([slowEnv](float* out, const float* in, size_t frameCount) {
+ slowEnv->process(out, in, frameCount);
+ });
+
+ auto bsf = ::android::audio_effect::haptic_generator::createBSF(
+ mParams.mVibratorInfo.resonantFrequencyHz, mParams.mVibratorInfo.qFactor,
+ mParams.mVibratorInfo.qFactor / 2.0f, mSampleRate, channelCount);
+ mProcessorsRecord.bsf = bsf;
+ addBiquadFilter(bsf);
+
+ // The process chain captures the shared pointer of the Distortion in lambda. It will
+ // be the only reference to the Distortion.
+ // The process record will keep a weak pointer to the Distortion so that it is possible
+ // to access the Distortion outside of the process chain.
+ auto distortion = std::make_shared<::android::audio_effect::haptic_generator::Distortion>(
+ DEFAULT_DISTORTION_CORNER_FREQUENCY, mSampleRate, DEFAULT_DISTORTION_INPUT_GAIN,
+ DEFAULT_DISTORTION_CUBE_THRESHOLD, getDistortionOutputGain(), channelCount);
+ mProcessorsRecord.distortions.push_back(distortion);
+ mProcessingChain.push_back([distortion](float* out, const float* in, size_t frameCount) {
+ distortion->process(out, in, frameCount);
+ });
+}
+
+void HapticGeneratorContext::configure() {
+ mProcessingChain.clear();
+ mProcessorsRecord.filters.clear();
+ mProcessorsRecord.ramps.clear();
+ mProcessorsRecord.slowEnvs.clear();
+ mProcessorsRecord.distortions.clear();
+
+ buildProcessingChain();
+}
+
+/**
+ * Run the processing chain to generate haptic data from audio data
+ *
+ * @param buf1 a buffer contains raw audio data
+ * @param buf2 a buffer that is large enough to keep all the data
+ * @param frameCount frame count of the data
+ *
+ * @return a pointer to the output buffer
+ */
+float* HapticGeneratorContext::runProcessingChain(float* buf1, float* buf2, size_t frameCount) {
+ float* in = buf1;
+ float* out = buf2;
+ for (const auto processingFunc : mProcessingChain) {
+ processingFunc(out, in, frameCount);
+ std::swap(in, out);
+ }
+ return in;
+}
+
+} // namespace aidl::android::hardware::audio::effect
diff --git a/media/libeffects/hapticgenerator/aidl/HapticGeneratorContext.h b/media/libeffects/hapticgenerator/aidl/HapticGeneratorContext.h
new file mode 100644
index 0000000..f16e2a4
--- /dev/null
+++ b/media/libeffects/hapticgenerator/aidl/HapticGeneratorContext.h
@@ -0,0 +1,124 @@
+/*
+ * 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 <vibrator/ExternalVibrationUtils.h>
+#include <map>
+
+#include "Processors.h"
+#include "effect-impl/EffectContext.h"
+
+namespace aidl::android::hardware::audio::effect {
+
+enum HapticGeneratorState {
+ HAPTIC_GENERATOR_STATE_UNINITIALIZED,
+ HAPTIC_GENERATOR_STATE_INITIALIZED,
+ HAPTIC_GENERATOR_STATE_ACTIVE,
+};
+
+struct HapticGeneratorParam {
+ // The audio channels used to generate haptic channels. The first channel will be used to
+ // generate HAPTIC_A, The second channel will be used to generate HAPTIC_B.
+ // The value will be offset of audio channel
+ int mHapticChannelSource[2];
+
+ int mHapticChannelCount;
+ int mAudioChannelCount;
+
+ HapticGenerator::HapticScale mHapticScale;
+ std::map<int, HapticGenerator::VibratorScale> mHapticScales;
+ // max intensity will be used to scale haptic data.
+ HapticGenerator::VibratorScale mMaxVibratorScale;
+
+ HapticGenerator::VibratorInformation mVibratorInfo;
+};
+
+// A structure to keep all shared pointers for all processors in HapticGenerator.
+struct HapticGeneratorProcessorsRecord {
+ std::vector<std::shared_ptr<HapticBiquadFilter>> filters;
+ std::vector<std::shared_ptr<::android::audio_effect::haptic_generator::Ramp>> ramps;
+ std::vector<std::shared_ptr<::android::audio_effect::haptic_generator::SlowEnvelope>> slowEnvs;
+ std::vector<std::shared_ptr<::android::audio_effect::haptic_generator::Distortion>> distortions;
+
+ // Cache band-pass filter and band-stop filter for updating parameters
+ // according to vibrator info
+ std::shared_ptr<HapticBiquadFilter> bpf;
+ std::shared_ptr<HapticBiquadFilter> bsf;
+};
+
+class HapticGeneratorContext final : public EffectContext {
+ public:
+ HapticGeneratorContext(int statusDepth, const Parameter::Common& common);
+ ~HapticGeneratorContext();
+ RetCode enable();
+ RetCode disable();
+ void reset();
+
+ RetCode setHgHapticScale(const HapticGenerator::HapticScale& hapticScale);
+ HapticGenerator::HapticScale getHgHapticScale() const { return mParams.mHapticScale; }
+
+ RetCode setHgVibratorInformation(const HapticGenerator::VibratorInformation& vibratorInfo);
+ HapticGenerator::VibratorInformation getHgVibratorInformation() const {
+ return mParams.mVibratorInfo;
+ }
+
+ IEffect::Status lvmProcess(float* in, float* out, int samples);
+
+ private:
+ static constexpr float DEFAULT_RESONANT_FREQUENCY = 150.0f;
+ static constexpr float DEFAULT_BSF_ZERO_Q = 8.0f;
+ static constexpr float DEFAULT_BSF_POLE_Q = 4.0f;
+ static constexpr float DEFAULT_DISTORTION_OUTPUT_GAIN = 1.5f;
+ static constexpr float DEFAULT_BPF_Q = 1.0f;
+ static constexpr float DEFAULT_SLOW_ENV_NORMALIZATION_POWER = -0.8f;
+ static constexpr float DEFAULT_DISTORTION_CORNER_FREQUENCY = 300.0f;
+ static constexpr float DEFAULT_DISTORTION_INPUT_GAIN = 0.3f;
+ static constexpr float DEFAULT_DISTORTION_CUBE_THRESHOLD = 0.1f;
+
+ HapticGeneratorState mState;
+ HapticGeneratorParam mParams;
+ int mSampleRate;
+ int mFrameCount = 0;
+
+ // A cache for all shared pointers of the HapticGenerator
+ struct HapticGeneratorProcessorsRecord mProcessorsRecord;
+
+ // Using a vector of functions to record the processing chain for haptic-generating algorithm.
+ // The three parameters of the processing functions are pointer to output buffer, pointer to
+ // input buffer and frame count.
+ std::vector<std::function<void(float*, const float*, size_t)>> mProcessingChain;
+
+ // inputBuffer is where to keep input buffer for the generating algorithm. It will be
+ // constructed according to hapticChannelSource.
+ std::vector<float> mInputBuffer;
+
+ // outputBuffer is a buffer having the same length as inputBuffer. It can be used as
+ // intermediate buffer in the generating algorithm.
+ std::vector<float> mOutputBuffer;
+
+ void init_params(media::audio::common::AudioChannelLayout inputChMask,
+ media::audio::common::AudioChannelLayout outputChMask);
+ void configure();
+
+ float getDistortionOutputGain();
+ float getFloatProperty(const std::string& key, float defaultValue);
+ void addBiquadFilter(std::shared_ptr<HapticBiquadFilter> filter);
+ void buildProcessingChain();
+ float* runProcessingChain(float* buf1, float* buf2, size_t frameCount);
+};
+
+} // namespace aidl::android::hardware::audio::effect