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.mMaxVibratorScale = 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 void 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 }
85 
setHgHapticScales(const std::vector<HapticGenerator::HapticScale> & hapticScales)86 RetCode HapticGeneratorContext::setHgHapticScales(
87         const std::vector<HapticGenerator::HapticScale>& hapticScales) {
88     for (auto hapticScale : hapticScales) {
89         mParams.mHapticScales.insert_or_assign(hapticScale.id, hapticScale.scale);
90     }
91     mParams.mMaxVibratorScale = HapticGenerator::VibratorScale::MUTE;
92     for (const auto& [id, vibratorScale] : mParams.mHapticScales) {
93         mParams.mMaxVibratorScale = std::max(mParams.mMaxVibratorScale, vibratorScale);
94     }
95     LOG(INFO) << " HapticGenerator VibratorScale set to " << toString(mParams.mMaxVibratorScale);
96     return RetCode::SUCCESS;
97 }
98 
getHgVibratorInformation() const99 HapticGenerator::VibratorInformation HapticGeneratorContext::getHgVibratorInformation() const {
100     return mParams.mVibratorInfo;
101 }
102 
getHgHapticScales() const103 std::vector<HapticGenerator::HapticScale> HapticGeneratorContext::getHgHapticScales() const {
104     std::vector<HapticGenerator::HapticScale> result;
105     for (const auto& [id, vibratorScale] : mParams.mHapticScales) {
106         result.push_back({id, vibratorScale});
107     }
108     return result;
109 }
110 
setHgVibratorInformation(const HapticGenerator::VibratorInformation & vibratorInfo)111 RetCode HapticGeneratorContext::setHgVibratorInformation(
112         const HapticGenerator::VibratorInformation& vibratorInfo) {
113     mParams.mVibratorInfo = vibratorInfo;
114     if (::android::audio_utils::safe_isnan(mParams.mVibratorInfo.resonantFrequencyHz)) {
115         LOG(WARNING) << __func__ << " resonantFrequencyHz reset from nan to "
116                      << DEFAULT_RESONANT_FREQUENCY;
117         mParams.mVibratorInfo.resonantFrequencyHz = DEFAULT_RESONANT_FREQUENCY;
118     }
119     if (::android::audio_utils::safe_isnan(mParams.mVibratorInfo.qFactor)) {
120         LOG(WARNING) << __func__ << " qFactor reset from nan to " << DEFAULT_BSF_ZERO_Q;
121         mParams.mVibratorInfo.qFactor = DEFAULT_BSF_ZERO_Q;
122     }
123 
124     if (mProcessorsRecord.bpf != nullptr) {
125         mProcessorsRecord.bpf->setCoefficients(::android::audio_effect::haptic_generator::bpfCoefs(
126                 mParams.mVibratorInfo.resonantFrequencyHz, DEFAULT_BPF_Q, mSampleRate));
127     }
128     if (mProcessorsRecord.bsf != nullptr) {
129         mProcessorsRecord.bsf->setCoefficients(::android::audio_effect::haptic_generator::bsfCoefs(
130                 mParams.mVibratorInfo.resonantFrequencyHz, mParams.mVibratorInfo.qFactor,
131                 mParams.mVibratorInfo.qFactor / 2.0f, mSampleRate));
132     }
133 
134     configure();
135     return RetCode::SUCCESS;
136 }
137 
process(float * in,float * out,int samples)138 IEffect::Status HapticGeneratorContext::process(float* in, float* out, int samples) {
139     IEffect::Status status = {EX_NULL_POINTER, 0, 0};
140     RETURN_VALUE_IF(!in, status, "nullInput");
141     RETURN_VALUE_IF(!out, status, "nullOutput");
142     status = {EX_ILLEGAL_STATE, 0, 0};
143     RETURN_VALUE_IF(getInputFrameSize() != getOutputFrameSize(), status, "FrameSizeMismatch");
144     auto frameSize = getInputFrameSize();
145     RETURN_VALUE_IF(0 == frameSize, status, "zeroFrameSize");
146 
147     if (mState != HAPTIC_GENERATOR_STATE_ACTIVE) {
148         LOG(WARNING) << " HapticGenerator in wrong state " << mState;
149         return status;
150     }
151 
152     if (mParams.mMaxVibratorScale == HapticGenerator::VibratorScale::MUTE) {
153         // Haptic channels are muted, not need to generate haptic data.
154         return {STATUS_OK, samples, samples};
155     }
156 
157     // Resize buffer if the haptic sample count is greater than buffer size.
158     const size_t hapticSampleCount = mFrameCount * mParams.mHapticChannelCount;
159     const size_t audioSampleCount = mFrameCount * mParams.mAudioChannelCount;
160     if (hapticSampleCount > mInputBuffer.size()) {
161         // The inputBuffer and outputBuffer must have the same size, which must be at least
162         // the haptic sample count.
163         mInputBuffer.resize(hapticSampleCount);
164         mOutputBuffer.resize(hapticSampleCount);
165     }
166 
167     // Construct input buffer according to haptic channel source
168     for (int64_t i = 0; i < mFrameCount; ++i) {
169         for (int j = 0; j < mParams.mHapticChannelCount; ++j) {
170             mInputBuffer[i * mParams.mHapticChannelCount + j] =
171                     in[i * mParams.mAudioChannelCount + mParams.mHapticChannelSource[j]];
172         }
173     }
174 
175     float* hapticOutBuffer =
176             runProcessingChain(mInputBuffer.data(), mOutputBuffer.data(), mFrameCount);
177     ::android::os::scaleHapticData(
178             hapticOutBuffer, hapticSampleCount,
179             {static_cast<::android::os::HapticLevel>(mParams.mMaxVibratorScale)} /* scale */,
180             mParams.mVibratorInfo.maxAmplitude /* limit */);
181 
182     // For haptic data, the haptic playback thread will copy the data from effect input
183     // buffer, which contains haptic data at the end of the buffer, directly to sink buffer.
184     // In AIDL only output buffer is send back to the audio framework via FMQ. Here the effect copy
185     // the generated haptic data to the target position of output buffer, the framework then append
186     // it to the same position of input buffer.
187     memcpy_to_float_from_float_with_clamping(out + audioSampleCount, hapticOutBuffer,
188                                              hapticSampleCount, 2.f /* absMax */);
189     return {STATUS_OK, samples, samples};
190 }
191 
init_params(const Parameter::Common & common)192 void HapticGeneratorContext::init_params(const Parameter::Common& common) {
193     mSampleRate = common.input.base.sampleRate;
194     mFrameCount = common.input.frameCount;
195 
196     mParams.mAudioChannelCount = ::aidl::android::hardware::audio::common::getChannelCount(
197             common.input.base.channelMask,
198             ~media::audio::common::AudioChannelLayout::LAYOUT_HAPTIC_AB);
199     mParams.mHapticChannelCount = ::aidl::android::hardware::audio::common::getChannelCount(
200             common.output.base.channelMask,
201             media::audio::common::AudioChannelLayout::LAYOUT_HAPTIC_AB);
202     LOG_ALWAYS_FATAL_IF(mParams.mHapticChannelCount > 2, "haptic channel count is too large");
203     for (int i = 0; i < mParams.mHapticChannelCount; ++i) {
204         // By default, use the first audio channel to generate haptic channels.
205         mParams.mHapticChannelSource[i] = 0;
206     }
207     configure();
208     LOG(DEBUG) << " HapticGenerator init context:\n" << contextToString();
209 }
210 
getDistortionOutputGain() const211 float HapticGeneratorContext::getDistortionOutputGain() const {
212     float distortionOutputGain = getFloatProperty(
213             "vendor.audio.hapticgenerator.distortion.output.gain", DEFAULT_DISTORTION_OUTPUT_GAIN);
214     return distortionOutputGain;
215 }
216 
getFloatProperty(const std::string & key,float defaultValue) const217 float HapticGeneratorContext::getFloatProperty(const std::string& key, float defaultValue) const {
218     float result;
219     std::string value = ::android::base::GetProperty(key, "");
220     if (!value.empty() && ::android::base::ParseFloat(value, &result)) {
221         return result;
222     }
223     return defaultValue;
224 }
225 
addBiquadFilter(std::shared_ptr<HapticBiquadFilter> filter)226 void HapticGeneratorContext::addBiquadFilter(std::shared_ptr<HapticBiquadFilter> filter) {
227     // The process chain captures the shared pointer of the filter in lambda.
228     // The process record will keep a shared pointer to the filter so that it is possible to
229     // access the filter outside of the process chain.
230     mProcessorsRecord.filters.push_back(filter);
231     mProcessingChain.push_back([filter](float* out, const float* in, size_t frameCount) {
232         filter->process(out, in, frameCount);
233     });
234 }
235 
236 /**
237  * Build haptic generator processing chain.
238  */
buildProcessingChain()239 void HapticGeneratorContext::buildProcessingChain() {
240     const size_t channelCount = mParams.mHapticChannelCount;
241     float highPassCornerFrequency = 50.0f;
242     auto hpf = ::android::audio_effect::haptic_generator::createHPF2(highPassCornerFrequency,
243                                                                      mSampleRate, channelCount);
244     addBiquadFilter(hpf);
245     float lowPassCornerFrequency = 9000.0f;
246     auto lpf = ::android::audio_effect::haptic_generator::createLPF2(lowPassCornerFrequency,
247                                                                      mSampleRate, channelCount);
248     addBiquadFilter(lpf);
249 
250     auto ramp = std::make_shared<::android::audio_effect::haptic_generator::Ramp>(
251             channelCount);  // ramp = half-wave rectifier.
252     // The process chain captures the shared pointer of the ramp in lambda. It will be the only
253     // reference to the ramp.
254     // The process record will keep a weak pointer to the ramp so that it is possible to access
255     // the ramp outside of the process chain.
256     mProcessorsRecord.ramps.push_back(ramp);
257     mProcessingChain.push_back([ramp](float* out, const float* in, size_t frameCount) {
258         ramp->process(out, in, frameCount);
259     });
260 
261     highPassCornerFrequency = 60.0f;
262     hpf = ::android::audio_effect::haptic_generator::createHPF2(highPassCornerFrequency,
263                                                                 mSampleRate, channelCount);
264     addBiquadFilter(hpf);
265     lowPassCornerFrequency = 700.0f;
266     lpf = ::android::audio_effect::haptic_generator::createLPF2(lowPassCornerFrequency, mSampleRate,
267                                                                 channelCount);
268     addBiquadFilter(lpf);
269 
270     lowPassCornerFrequency = 400.0f;
271     lpf = ::android::audio_effect::haptic_generator::createLPF2(lowPassCornerFrequency, mSampleRate,
272                                                                 channelCount);
273     addBiquadFilter(lpf);
274     lowPassCornerFrequency = 500.0f;
275     lpf = ::android::audio_effect::haptic_generator::createLPF2(lowPassCornerFrequency, mSampleRate,
276                                                                 channelCount);
277     addBiquadFilter(lpf);
278 
279     auto bpf = ::android::audio_effect::haptic_generator::createBPF(
280             mParams.mVibratorInfo.resonantFrequencyHz, DEFAULT_BPF_Q, mSampleRate, channelCount);
281     mProcessorsRecord.bpf = bpf;
282     addBiquadFilter(bpf);
283 
284     float normalizationPower = DEFAULT_SLOW_ENV_NORMALIZATION_POWER;
285     // The process chain captures the shared pointer of the slow envelope in lambda. It will
286     // be the only reference to the slow envelope.
287     // The process record will keep a weak pointer to the slow envelope so that it is possible
288     // to access the slow envelope outside of the process chain.
289     // SlowEnvelope = partial normalizer, or AGC.
290     auto slowEnv = std::make_shared<::android::audio_effect::haptic_generator::SlowEnvelope>(
291             5.0f /*envCornerFrequency*/, mSampleRate, normalizationPower, 0.01f /*envOffset*/,
292             channelCount);
293     mProcessorsRecord.slowEnvs.push_back(slowEnv);
294     mProcessingChain.push_back([slowEnv](float* out, const float* in, size_t frameCount) {
295         slowEnv->process(out, in, frameCount);
296     });
297 
298     auto bsf = ::android::audio_effect::haptic_generator::createBSF(
299             mParams.mVibratorInfo.resonantFrequencyHz, mParams.mVibratorInfo.qFactor,
300             mParams.mVibratorInfo.qFactor / 2.0f, mSampleRate, channelCount);
301     mProcessorsRecord.bsf = bsf;
302     addBiquadFilter(bsf);
303 
304     // The process chain captures the shared pointer of the Distortion in lambda. It will
305     // be the only reference to the Distortion.
306     // The process record will keep a weak pointer to the Distortion so that it is possible
307     // to access the Distortion outside of the process chain.
308     auto distortion = std::make_shared<::android::audio_effect::haptic_generator::Distortion>(
309             DEFAULT_DISTORTION_CORNER_FREQUENCY, mSampleRate, DEFAULT_DISTORTION_INPUT_GAIN,
310             DEFAULT_DISTORTION_CUBE_THRESHOLD, getDistortionOutputGain(), channelCount);
311     mProcessorsRecord.distortions.push_back(distortion);
312     mProcessingChain.push_back([distortion](float* out, const float* in, size_t frameCount) {
313         distortion->process(out, in, frameCount);
314     });
315 }
316 
configure()317 void HapticGeneratorContext::configure() {
318     mProcessingChain.clear();
319     mProcessorsRecord.filters.clear();
320     mProcessorsRecord.ramps.clear();
321     mProcessorsRecord.slowEnvs.clear();
322     mProcessorsRecord.distortions.clear();
323 
324     buildProcessingChain();
325 }
326 
327 /**
328  * Run the processing chain to generate haptic data from audio data
329  *
330  * @param buf1 a buffer contains raw audio data
331  * @param buf2 a buffer that is large enough to keep all the data
332  * @param frameCount frame count of the data
333  *
334  * @return a pointer to the output buffer
335  */
runProcessingChain(float * buf1,float * buf2,size_t frameCount)336 float* HapticGeneratorContext::runProcessingChain(float* buf1, float* buf2, size_t frameCount) {
337     float* in = buf1;
338     float* out = buf2;
339     for (const auto processingFunc : mProcessingChain) {
340         processingFunc(out, in, frameCount);
341         std::swap(in, out);
342     }
343     return in;
344 }
345 
paramToString(const struct HapticGeneratorParam & param) const346 std::string HapticGeneratorContext::paramToString(const struct HapticGeneratorParam& param) const {
347     std::stringstream ss;
348     ss << "\t\ttHapticGenerator Parameters:\n";
349     ss << "\t\t- mHapticChannelCount: " << param.mHapticChannelCount << '\n';
350     ss << "\t\t- mAudioChannelCount: " << param.mAudioChannelCount << '\n';
351     ss << "\t\t- mHapticChannelSource: " << param.mHapticChannelSource[0] << ", "
352        << param.mHapticChannelSource[1] << '\n';
353     ss << "\t\t- mMaxVibratorScale: " << ::android::internal::ToString(param.mMaxVibratorScale)
354        << '\n';
355     ss << "\t\t- mVibratorInfo: " << param.mVibratorInfo.toString() << '\n';
356     for (const auto& it : param.mHapticScales)
357         ss << "\t\t\t" << it.first << ": " << toString(it.second) << '\n';
358 
359     return ss.str();
360 }
361 
contextToString() const362 std::string HapticGeneratorContext::contextToString() const {
363     std::stringstream ss;
364     ss << "\t\tHapticGenerator Context:\n";
365     ss << "\t\t- state: " << mState << '\n';
366     ss << "\t\t- bpf Q: " << DEFAULT_BPF_Q << '\n';
367     ss << "\t\t- slow env normalization power: " << DEFAULT_SLOW_ENV_NORMALIZATION_POWER << '\n';
368     ss << "\t\t- distortion corner frequency: " << DEFAULT_DISTORTION_CORNER_FREQUENCY << '\n';
369     ss << "\t\t- distortion input gain: " << DEFAULT_DISTORTION_INPUT_GAIN << '\n';
370     ss << "\t\t- distortion cube threshold: " << DEFAULT_DISTORTION_CUBE_THRESHOLD << '\n';
371     ss << "\t\t- distortion output gain: " << getDistortionOutputGain() << '\n';
372     ss << "\t\tHapticGenerator Parameters:\n" << paramToString(mParams) << "\n";
373     return ss.str();
374 }
375 
376 }  // namespace aidl::android::hardware::audio::effect
377