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