xref: /aosp_15_r20/frameworks/av/media/libeffects/hapticgenerator/aidl/HapticGeneratorContext.cpp (revision ec779b8e0859a360c3d303172224686826e6e0e1)
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define LOG_TAG "AHAL_HapticGeneratorContext"
18 
19 #include "HapticGeneratorContext.h"
20 #include <android-base/logging.h>
21 #include <android-base/parsedouble.h>
22 #include <android-base/properties.h>
23 #include <audio_utils/primitives.h>
24 #include <audio_utils/safe_math.h>
25 #include <Utils.h>
26 
27 #include <cstddef>
28 
29 using aidl::android::hardware::audio::common::getChannelCount;
30 using aidl::android::hardware::audio::common::getPcmSampleSizeInBytes;
31 using aidl::android::media::audio::common::AudioChannelLayout;
32 
33 namespace aidl::android::hardware::audio::effect {
34 
HapticGeneratorContext(int statusDepth,const Parameter::Common & common)35 HapticGeneratorContext::HapticGeneratorContext(int statusDepth, const Parameter::Common& common)
36     : EffectContext(statusDepth, common) {
37     mState = HAPTIC_GENERATOR_STATE_UNINITIALIZED;
38 
39     mParams.mMaxHapticScale = {.scale = HapticGenerator::VibratorScale::MUTE};
40     mParams.mVibratorInfo.resonantFrequencyHz = DEFAULT_RESONANT_FREQUENCY;
41     mParams.mVibratorInfo.qFactor = DEFAULT_BSF_ZERO_Q;
42     mParams.mVibratorInfo.maxAmplitude = 0.f;
43 
44     init_params(common);
45     mState = HAPTIC_GENERATOR_STATE_INITIALIZED;
46 }
47 
~HapticGeneratorContext()48 HapticGeneratorContext::~HapticGeneratorContext() {
49     mState = HAPTIC_GENERATOR_STATE_UNINITIALIZED;
50 }
51 
52 // Override EffectImpl::setCommon for HapticGenerator because we need init_params
setCommon(const Parameter::Common & common)53 RetCode HapticGeneratorContext::setCommon(const Parameter::Common& common) {
54     init_params(common);
55     return EffectContext::setCommon(common);
56 }
57 
enable()58 RetCode HapticGeneratorContext::enable() {
59     if (mState != HAPTIC_GENERATOR_STATE_INITIALIZED) {
60         return RetCode::ERROR_EFFECT_LIB_ERROR;
61     }
62     mState = HAPTIC_GENERATOR_STATE_ACTIVE;
63     return RetCode::SUCCESS;
64 }
65 
disable()66 RetCode HapticGeneratorContext::disable() {
67     if (mState != HAPTIC_GENERATOR_STATE_ACTIVE) {
68         return RetCode::ERROR_EFFECT_LIB_ERROR;
69     }
70     mState = HAPTIC_GENERATOR_STATE_INITIALIZED;
71     return RetCode::SUCCESS;
72 }
73 
reset()74 RetCode HapticGeneratorContext::reset() {
75     for (auto& filter : mProcessorsRecord.filters) {
76         filter->clear();
77     }
78     for (auto& slowEnv : mProcessorsRecord.slowEnvs) {
79         slowEnv->clear();
80     }
81     for (auto& distortion : mProcessorsRecord.distortions) {
82         distortion->clear();
83     }
84     return RetCode::SUCCESS;
85 }
86 
setHgHapticScales(const std::vector<HapticGenerator::HapticScale> & hapticScales)87 RetCode HapticGeneratorContext::setHgHapticScales(
88         const std::vector<HapticGenerator::HapticScale>& hapticScales) {
89     for (auto hapticScale : hapticScales) {
90         mParams.mHapticScales.insert_or_assign(hapticScale.id, hapticScale);
91     }
92     mParams.mMaxHapticScale = {.scale = HapticGenerator::VibratorScale::MUTE};
93     for (const auto& [id, vibratorScale] : mParams.mHapticScales) {
94         // TODO(b/360314386): update to use new scale factors
95         if (vibratorScale.scale > mParams.mMaxHapticScale.scale) {
96             mParams.mMaxHapticScale = vibratorScale;
97         }
98     }
99     LOG(INFO) << " HapticGenerator VibratorScale set to "
100               << toString(mParams.mMaxHapticScale.scale);
101     return RetCode::SUCCESS;
102 }
103 
getHgVibratorInformation() const104 HapticGenerator::VibratorInformation HapticGeneratorContext::getHgVibratorInformation() const {
105     return mParams.mVibratorInfo;
106 }
107 
getHgHapticScales() const108 std::vector<HapticGenerator::HapticScale> HapticGeneratorContext::getHgHapticScales() const {
109     std::vector<HapticGenerator::HapticScale> result;
110     for (const auto& [_, hapticScale] : mParams.mHapticScales) {
111         result.push_back(hapticScale);
112     }
113     return result;
114 }
115 
setHgVibratorInformation(const HapticGenerator::VibratorInformation & vibratorInfo)116 RetCode HapticGeneratorContext::setHgVibratorInformation(
117         const HapticGenerator::VibratorInformation& vibratorInfo) {
118     mParams.mVibratorInfo = vibratorInfo;
119     if (::android::audio_utils::safe_isnan(mParams.mVibratorInfo.resonantFrequencyHz)) {
120         LOG(WARNING) << __func__ << " resonantFrequencyHz reset from nan to "
121                      << DEFAULT_RESONANT_FREQUENCY;
122         mParams.mVibratorInfo.resonantFrequencyHz = DEFAULT_RESONANT_FREQUENCY;
123     }
124     if (::android::audio_utils::safe_isnan(mParams.mVibratorInfo.qFactor)) {
125         LOG(WARNING) << __func__ << " qFactor reset from nan to " << DEFAULT_BSF_ZERO_Q;
126         mParams.mVibratorInfo.qFactor = DEFAULT_BSF_ZERO_Q;
127     }
128 
129     if (mProcessorsRecord.bpf != nullptr) {
130         mProcessorsRecord.bpf->setCoefficients(::android::audio_effect::haptic_generator::bpfCoefs(
131                 mParams.mVibratorInfo.resonantFrequencyHz, DEFAULT_BPF_Q, mSampleRate));
132     }
133     if (mProcessorsRecord.bsf != nullptr) {
134         mProcessorsRecord.bsf->setCoefficients(::android::audio_effect::haptic_generator::bsfCoefs(
135                 mParams.mVibratorInfo.resonantFrequencyHz, mParams.mVibratorInfo.qFactor,
136                 mParams.mVibratorInfo.qFactor / 2.0f, mSampleRate));
137     }
138 
139     configure();
140     return RetCode::SUCCESS;
141 }
142 
process(float * in,float * out,int samples)143 IEffect::Status HapticGeneratorContext::process(float* in, float* out, int samples) {
144     IEffect::Status status = {EX_NULL_POINTER, 0, 0};
145     RETURN_VALUE_IF(!in, status, "nullInput");
146     RETURN_VALUE_IF(!out, status, "nullOutput");
147     status = {EX_ILLEGAL_STATE, 0, 0};
148     RETURN_VALUE_IF(getInputFrameSize() != getOutputFrameSize(), status, "FrameSizeMismatch");
149     auto frameSize = getInputFrameSize();
150     RETURN_VALUE_IF(0 == frameSize, status, "zeroFrameSize");
151 
152     if (mState != HAPTIC_GENERATOR_STATE_ACTIVE) {
153         LOG(WARNING) << " HapticGenerator in wrong state " << mState;
154         return status;
155     }
156 
157     if (mParams.mMaxHapticScale.scale == HapticGenerator::VibratorScale::MUTE) {
158         // Haptic channels are muted, not need to generate haptic data.
159         return {STATUS_OK, samples, samples};
160     }
161 
162     // Resize buffer if the haptic sample count is greater than buffer size.
163     const size_t hapticSampleCount = mFrameCount * mParams.mHapticChannelCount;
164     const size_t audioSampleCount = mFrameCount * mParams.mAudioChannelCount;
165     if (hapticSampleCount > mInputBuffer.size()) {
166         // The inputBuffer and outputBuffer must have the same size, which must be at least
167         // the haptic sample count.
168         mInputBuffer.resize(hapticSampleCount);
169         mOutputBuffer.resize(hapticSampleCount);
170     }
171 
172     // Construct input buffer according to haptic channel source
173     for (int64_t i = 0; i < mFrameCount; ++i) {
174         for (int j = 0; j < mParams.mHapticChannelCount; ++j) {
175             mInputBuffer[i * mParams.mHapticChannelCount + j] =
176                     in[i * mParams.mAudioChannelCount + mParams.mHapticChannelSource[j]];
177         }
178     }
179 
180     float* hapticOutBuffer =
181             runProcessingChain(mInputBuffer.data(), mOutputBuffer.data(), mFrameCount);
182     ::android::os::scaleHapticData(
183             hapticOutBuffer, hapticSampleCount,
184             ::android::os::HapticScale(
185                     static_cast<::android::os::HapticLevel>(mParams.mMaxHapticScale.scale),
186                     mParams.mMaxHapticScale.scaleFactor,
187                     mParams.mMaxHapticScale.adaptiveScaleFactor),
188             mParams.mVibratorInfo.maxAmplitude /* limit */);
189 
190     // For haptic data, the haptic playback thread will copy the data from effect input
191     // buffer, which contains haptic data at the end of the buffer, directly to sink buffer.
192     // In AIDL only output buffer is send back to the audio framework via FMQ. Here the effect copy
193     // the generated haptic data to the target position of output buffer, the framework then append
194     // it to the same position of input buffer.
195     memcpy_to_float_from_float_with_clamping(out + audioSampleCount, hapticOutBuffer,
196                                              hapticSampleCount, 2.f /* absMax */);
197     return {STATUS_OK, samples, samples};
198 }
199 
init_params(const Parameter::Common & common)200 void HapticGeneratorContext::init_params(const Parameter::Common& common) {
201     mSampleRate = common.input.base.sampleRate;
202     mFrameCount = common.input.frameCount;
203 
204     mParams.mAudioChannelCount = ::aidl::android::hardware::audio::common::getChannelCount(
205             common.input.base.channelMask,
206             ~media::audio::common::AudioChannelLayout::LAYOUT_HAPTIC_AB);
207     mParams.mHapticChannelCount = ::aidl::android::hardware::audio::common::getChannelCount(
208             common.output.base.channelMask,
209             media::audio::common::AudioChannelLayout::LAYOUT_HAPTIC_AB);
210     LOG_ALWAYS_FATAL_IF(mParams.mHapticChannelCount > 2, "haptic channel count is too large");
211     for (int i = 0; i < mParams.mHapticChannelCount; ++i) {
212         // By default, use the first audio channel to generate haptic channels.
213         mParams.mHapticChannelSource[i] = 0;
214     }
215     configure();
216     LOG(DEBUG) << " HapticGenerator init context:\n" << contextToString();
217 }
218 
getDistortionOutputGain() const219 float HapticGeneratorContext::getDistortionOutputGain() const {
220     float distortionOutputGain = getFloatProperty(
221             "vendor.audio.hapticgenerator.distortion.output.gain", DEFAULT_DISTORTION_OUTPUT_GAIN);
222     return distortionOutputGain;
223 }
224 
getFloatProperty(const std::string & key,float defaultValue) const225 float HapticGeneratorContext::getFloatProperty(const std::string& key, float defaultValue) const {
226     float result;
227     std::string value = ::android::base::GetProperty(key, "");
228     if (!value.empty() && ::android::base::ParseFloat(value, &result)) {
229         return result;
230     }
231     return defaultValue;
232 }
233 
addBiquadFilter(std::shared_ptr<HapticBiquadFilter> filter)234 void HapticGeneratorContext::addBiquadFilter(std::shared_ptr<HapticBiquadFilter> filter) {
235     // The process chain captures the shared pointer of the filter in lambda.
236     // The process record will keep a shared pointer to the filter so that it is possible to
237     // access the filter outside of the process chain.
238     mProcessorsRecord.filters.push_back(filter);
239     mProcessingChain.push_back([filter](float* out, const float* in, size_t frameCount) {
240         filter->process(out, in, frameCount);
241     });
242 }
243 
244 /**
245  * Build haptic generator processing chain.
246  */
buildProcessingChain()247 void HapticGeneratorContext::buildProcessingChain() {
248     const size_t channelCount = mParams.mHapticChannelCount;
249     float highPassCornerFrequency = 50.0f;
250     auto hpf = ::android::audio_effect::haptic_generator::createHPF2(highPassCornerFrequency,
251                                                                      mSampleRate, channelCount);
252     addBiquadFilter(hpf);
253     float lowPassCornerFrequency = 9000.0f;
254     auto lpf = ::android::audio_effect::haptic_generator::createLPF2(lowPassCornerFrequency,
255                                                                      mSampleRate, channelCount);
256     addBiquadFilter(lpf);
257 
258     auto ramp = std::make_shared<::android::audio_effect::haptic_generator::Ramp>(
259             channelCount);  // ramp = half-wave rectifier.
260     // The process chain captures the shared pointer of the ramp in lambda. It will be the only
261     // reference to the ramp.
262     // The process record will keep a weak pointer to the ramp so that it is possible to access
263     // the ramp outside of the process chain.
264     mProcessorsRecord.ramps.push_back(ramp);
265     mProcessingChain.push_back([ramp](float* out, const float* in, size_t frameCount) {
266         ramp->process(out, in, frameCount);
267     });
268 
269     highPassCornerFrequency = 60.0f;
270     hpf = ::android::audio_effect::haptic_generator::createHPF2(highPassCornerFrequency,
271                                                                 mSampleRate, channelCount);
272     addBiquadFilter(hpf);
273     lowPassCornerFrequency = 700.0f;
274     lpf = ::android::audio_effect::haptic_generator::createLPF2(lowPassCornerFrequency, mSampleRate,
275                                                                 channelCount);
276     addBiquadFilter(lpf);
277 
278     lowPassCornerFrequency = 400.0f;
279     lpf = ::android::audio_effect::haptic_generator::createLPF2(lowPassCornerFrequency, mSampleRate,
280                                                                 channelCount);
281     addBiquadFilter(lpf);
282     lowPassCornerFrequency = 500.0f;
283     lpf = ::android::audio_effect::haptic_generator::createLPF2(lowPassCornerFrequency, mSampleRate,
284                                                                 channelCount);
285     addBiquadFilter(lpf);
286 
287     auto bpf = ::android::audio_effect::haptic_generator::createBPF(
288             mParams.mVibratorInfo.resonantFrequencyHz, DEFAULT_BPF_Q, mSampleRate, channelCount);
289     mProcessorsRecord.bpf = bpf;
290     addBiquadFilter(bpf);
291 
292     float normalizationPower = DEFAULT_SLOW_ENV_NORMALIZATION_POWER;
293     // The process chain captures the shared pointer of the slow envelope in lambda. It will
294     // be the only reference to the slow envelope.
295     // The process record will keep a weak pointer to the slow envelope so that it is possible
296     // to access the slow envelope outside of the process chain.
297     // SlowEnvelope = partial normalizer, or AGC.
298     auto slowEnv = std::make_shared<::android::audio_effect::haptic_generator::SlowEnvelope>(
299             5.0f /*envCornerFrequency*/, mSampleRate, normalizationPower, 0.01f /*envOffset*/,
300             channelCount);
301     mProcessorsRecord.slowEnvs.push_back(slowEnv);
302     mProcessingChain.push_back([slowEnv](float* out, const float* in, size_t frameCount) {
303         slowEnv->process(out, in, frameCount);
304     });
305 
306     auto bsf = ::android::audio_effect::haptic_generator::createBSF(
307             mParams.mVibratorInfo.resonantFrequencyHz, mParams.mVibratorInfo.qFactor,
308             mParams.mVibratorInfo.qFactor / 2.0f, mSampleRate, channelCount);
309     mProcessorsRecord.bsf = bsf;
310     addBiquadFilter(bsf);
311 
312     // The process chain captures the shared pointer of the Distortion in lambda. It will
313     // be the only reference to the Distortion.
314     // The process record will keep a weak pointer to the Distortion so that it is possible
315     // to access the Distortion outside of the process chain.
316     auto distortion = std::make_shared<::android::audio_effect::haptic_generator::Distortion>(
317             DEFAULT_DISTORTION_CORNER_FREQUENCY, mSampleRate, DEFAULT_DISTORTION_INPUT_GAIN,
318             DEFAULT_DISTORTION_CUBE_THRESHOLD, getDistortionOutputGain(), channelCount);
319     mProcessorsRecord.distortions.push_back(distortion);
320     mProcessingChain.push_back([distortion](float* out, const float* in, size_t frameCount) {
321         distortion->process(out, in, frameCount);
322     });
323 }
324 
configure()325 void HapticGeneratorContext::configure() {
326     mProcessingChain.clear();
327     mProcessorsRecord.filters.clear();
328     mProcessorsRecord.ramps.clear();
329     mProcessorsRecord.slowEnvs.clear();
330     mProcessorsRecord.distortions.clear();
331 
332     buildProcessingChain();
333 }
334 
335 /**
336  * Run the processing chain to generate haptic data from audio data
337  *
338  * @param buf1 a buffer contains raw audio data
339  * @param buf2 a buffer that is large enough to keep all the data
340  * @param frameCount frame count of the data
341  *
342  * @return a pointer to the output buffer
343  */
runProcessingChain(float * buf1,float * buf2,size_t frameCount)344 float* HapticGeneratorContext::runProcessingChain(float* buf1, float* buf2, size_t frameCount) {
345     float* in = buf1;
346     float* out = buf2;
347     for (const auto processingFunc : mProcessingChain) {
348         processingFunc(out, in, frameCount);
349         std::swap(in, out);
350     }
351     return in;
352 }
353 
paramToString(const struct HapticGeneratorParam & param) const354 std::string HapticGeneratorContext::paramToString(const struct HapticGeneratorParam& param) const {
355     std::stringstream ss;
356     ss << "\t\tHapticGenerator Parameters:\n";
357     ss << "\t\t- mHapticChannelCount: " << param.mHapticChannelCount << '\n';
358     ss << "\t\t- mAudioChannelCount: " << param.mAudioChannelCount << '\n';
359     ss << "\t\t- mHapticChannelSource: " << param.mHapticChannelSource[0] << ", "
360        << param.mHapticChannelSource[1] << '\n';
361     ss << "\t\t- mMaxHapticScale: " << ::android::internal::ToString(param.mMaxHapticScale.scale)
362        << ", scaleFactor=" << param.mMaxHapticScale.scaleFactor
363        << ", adaptiveScaleFactor=" << param.mMaxHapticScale.adaptiveScaleFactor << '\n';
364     ss << "\t\t- mVibratorInfo: " << param.mVibratorInfo.toString() << '\n';
365     for (const auto& it : param.mHapticScales)
366         ss << "\t\t\t" << it.first << ": " << toString(it.second.scale) << '\n';
367 
368     return ss.str();
369 }
370 
contextToString() const371 std::string HapticGeneratorContext::contextToString() const {
372     std::stringstream ss;
373     ss << "\t\tHapticGenerator Context:\n";
374     ss << "\t\t- state: " << mState << '\n';
375     ss << "\t\t- bpf Q: " << DEFAULT_BPF_Q << '\n';
376     ss << "\t\t- slow env normalization power: " << DEFAULT_SLOW_ENV_NORMALIZATION_POWER << '\n';
377     ss << "\t\t- distortion corner frequency: " << DEFAULT_DISTORTION_CORNER_FREQUENCY << '\n';
378     ss << "\t\t- distortion input gain: " << DEFAULT_DISTORTION_INPUT_GAIN << '\n';
379     ss << "\t\t- distortion cube threshold: " << DEFAULT_DISTORTION_CUBE_THRESHOLD << '\n';
380     ss << "\t\t- distortion output gain: " << getDistortionOutputGain() << '\n';
381     ss << paramToString(mParams) << "\n";
382     return ss.str();
383 }
384 
385 }  // namespace aidl::android::hardware::audio::effect
386