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