/* * 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. */ #include #include #include #define LOG_TAG "VtsHalHapticGeneratorTargetTest" #include #include #include #include "EffectHelper.h" using namespace android; using aidl::android::hardware::audio::common::getChannelCount; using aidl::android::hardware::audio::effect::Descriptor; using aidl::android::hardware::audio::effect::getEffectTypeUuidHapticGenerator; using aidl::android::hardware::audio::effect::HapticGenerator; using aidl::android::hardware::audio::effect::IEffect; using aidl::android::hardware::audio::effect::IFactory; using aidl::android::hardware::audio::effect::Parameter; using android::hardware::audio::common::testing::detail::TestExecutionTracer; const int MIN_ID = std::numeric_limits::min(); const int MAX_ID = std::numeric_limits::max(); const float MIN_FLOAT = std::numeric_limits::min(); const float MAX_FLOAT = std::numeric_limits::max(); std::vector kScaleValues = { ndk::enum_range().begin(), ndk::enum_range().end()}; const std::vector kScaleFactorValues = {HapticGenerator::HapticScale::UNDEFINED_SCALE_FACTOR, 0.0f, 0.5f, 1.0f, MAX_FLOAT}; const std::vector kAdaptiveScaleFactorValues = { HapticGenerator::HapticScale::UNDEFINED_SCALE_FACTOR, 0.0f, 0.5f, 1.0f, MAX_FLOAT}; const std::vector kResonantFrequencyValues = {MIN_FLOAT, 100, MAX_FLOAT}; const std::vector kQFactorValues = {MIN_FLOAT, 100, MAX_FLOAT}; const std::vector kMaxAmplitude = {MIN_FLOAT, 100, MAX_FLOAT}; constexpr int HAPTIC_SCALE_FACTORS_EFFECT_MIN_VERSION = 3; static const std::vector kHapticOutputLayouts = { AudioChannelLayout::LAYOUT_MONO_HAPTIC_A, AudioChannelLayout::LAYOUT_MONO_HAPTIC_AB, AudioChannelLayout::LAYOUT_STEREO_HAPTIC_A, AudioChannelLayout::LAYOUT_STEREO_HAPTIC_AB}; class HapticGeneratorHelper : public EffectHelper { public: void SetUpHapticGenerator(int32_t chMask = AudioChannelLayout::CHANNEL_HAPTIC_A) { ASSERT_NE(nullptr, mFactory); ASSERT_NO_FATAL_FAILURE(create(mFactory, mEffect, mDescriptor)); EXPECT_STATUS(EX_NONE, mEffect->getInterfaceVersion(&mEffectInterfaceVersion)); AudioChannelLayout layout = AudioChannelLayout::make(chMask); Parameter::Common common = createParamCommon( 0 /* session */, 1 /* ioHandle */, kSamplingFrequency /* iSampleRate */, kSamplingFrequency /* oSampleRate */, kFrameCount /* iFrameCount */, kFrameCount /* oFrameCount */, layout, layout); ASSERT_NO_FATAL_FAILURE(open(mEffect, common, std::nullopt, &ret, EX_NONE)); ASSERT_NE(nullptr, mEffect); } void TearDownHapticGenerator() { ASSERT_NO_FATAL_FAILURE(close(mEffect)); ASSERT_NO_FATAL_FAILURE(destroy(mFactory, mEffect)); ret = IEffect::OpenEffectReturn{}; } Parameter createScaleParam(const std::vector& hapticScales) { return Parameter::make( Parameter::Specific::make( HapticGenerator::make(hapticScales))); } Parameter createVibratorParam(HapticGenerator::VibratorInformation vibrationInfo) { return Parameter::make( Parameter::Specific::make( HapticGenerator::make(vibrationInfo))); } void setAndVerifyParameter(Parameter hapticParameter, HapticGenerator::Tag tag, binder_exception_t expected = EX_NONE) { EXPECT_STATUS(expected, mEffect->setParameter(hapticParameter)) << hapticParameter.toString(); if (expected == EX_NONE) { // get parameter Parameter getParam; auto second = Parameter::Id::make( HapticGenerator::Id::make( HapticGenerator::Tag(tag))); // If the set is successful, get param should match EXPECT_STATUS(expected, mEffect->getParameter(second, &getParam)); EXPECT_EQ(hapticParameter, getParam) << "\nexpectedParam:" << hapticParameter.toString() << "\ngetParam:" << getParam.toString(); } } HapticGenerator::VibratorInformation createVibratorInfo(float resonantFrequency, float qFactor, float amplitude) { return HapticGenerator::VibratorInformation(resonantFrequency, qFactor, amplitude); } static const long kFrameCount = 10000; static constexpr int kSamplingFrequency = 44100; static constexpr int kDefaultScaleID = 0; static constexpr float kDefaultMaxAmp = 1; static constexpr float kDefaultResonantFrequency = 150; static constexpr float kDefaultQfactor = 8; static constexpr HapticGenerator::VibratorScale kDefaultScale = HapticGenerator::VibratorScale::NONE; std::shared_ptr mFactory; std::shared_ptr mEffect; IEffect::OpenEffectReturn ret; Parameter mHapticSpecificParameter; Parameter::Id mHapticIdParameter; int mEffectInterfaceVersion; }; /** *Tests do the following: * -Testing parameter range supported by the effect. * -For any supported value test expects EX_NONE from IEffect.setParameter(), * otherwise expect EX_ILLEGAL_ARGUMENT. * -Validating the effect by comparing the output energies of the supported parameters. **/ using EffectInstance = std::pair, Descriptor>; class HapticGeneratorScaleParamTest : public ::testing::TestWithParam, public HapticGeneratorHelper { public: HapticGeneratorScaleParamTest() { std::tie(mFactory, mDescriptor) = GetParam(); } void SetUp() override { ASSERT_NO_FATAL_FAILURE(SetUpHapticGenerator()); } void TearDown() override { ASSERT_NO_FATAL_FAILURE(TearDownHapticGenerator()); } }; TEST_P(HapticGeneratorScaleParamTest, SetAndGetScales) { std::vector hapticScales; for (int i = 0; i < static_cast(kScaleValues.size()); i++) { hapticScales.push_back({.id = i, .scale = kScaleValues[i]}); } ASSERT_NO_FATAL_FAILURE( setAndVerifyParameter(createScaleParam(hapticScales), HapticGenerator::hapticScales)); } TEST_P(HapticGeneratorScaleParamTest, SetAndGetScaleFactors) { if (mEffectInterfaceVersion < HAPTIC_SCALE_FACTORS_EFFECT_MIN_VERSION) { GTEST_SKIP() << "Skipping HapticGenerator ScaleFactors test for effect version " << std::to_string(mEffectInterfaceVersion); } std::vector hapticScales; for (int i = 0; i < static_cast(kScaleFactorValues.size()); i++) { hapticScales.push_back( {.id = i, .scale = kScaleValues[0], .scaleFactor = kScaleFactorValues[i]}); } ASSERT_NO_FATAL_FAILURE( setAndVerifyParameter(createScaleParam(hapticScales), HapticGenerator::hapticScales)); } TEST_P(HapticGeneratorScaleParamTest, SetAndGetAdaptiveScaleFactors) { if (mEffectInterfaceVersion < HAPTIC_SCALE_FACTORS_EFFECT_MIN_VERSION) { GTEST_SKIP() << "Skipping HapticGenerator AdaptiveScaleFactors test for effect version " << std::to_string(mEffectInterfaceVersion); } std::vector hapticScales; for (int i = 0; i < static_cast(kAdaptiveScaleFactorValues.size()); i++) { hapticScales.push_back({.id = i, .scale = kScaleValues[0], .scaleFactor = kScaleFactorValues[3], .adaptiveScaleFactor = kAdaptiveScaleFactorValues[i]}); } ASSERT_NO_FATAL_FAILURE( setAndVerifyParameter(createScaleParam(hapticScales), HapticGenerator::hapticScales)); } INSTANTIATE_TEST_SUITE_P( HapticGeneratorValidTest, HapticGeneratorScaleParamTest, testing::ValuesIn(EffectFactoryHelper::getAllEffectDescriptors( IFactory::descriptor, getEffectTypeUuidHapticGenerator())), [](const testing::TestParamInfo& info) { auto descriptor = info.param; return getPrefix(descriptor.second); }); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(HapticGeneratorScaleParamTest); enum VibratorParamName { VIBRATOR_PARAM_INSTANCE, VIBRATOR_PARAM_RESONANT_FREQUENCY, VIBRATOR_PARAM_Q_FACTOR, VIBRATOR_PARAM_MAX_AMPLITUDE, }; using HapticGeneratorVibratorInfoTestParam = std::tuple; class HapticGeneratorVibratorInfoParamTest : public ::testing::TestWithParam, public HapticGeneratorHelper { public: HapticGeneratorVibratorInfoParamTest() : mParamResonantFrequency(std::get(GetParam())), mParamQFactor(std::get(GetParam())), mParamMaxAmplitude(std::get(GetParam())) { std::tie(mFactory, mDescriptor) = std::get(GetParam()); } void SetUp() override { ASSERT_NO_FATAL_FAILURE(SetUpHapticGenerator()); } void TearDown() override { ASSERT_NO_FATAL_FAILURE(TearDownHapticGenerator()); } float mParamResonantFrequency = kDefaultResonantFrequency; float mParamQFactor = kDefaultQfactor; float mParamMaxAmplitude = kDefaultMaxAmp; }; TEST_P(HapticGeneratorVibratorInfoParamTest, SetAndGetVibratorInformation) { auto vibratorInfo = createVibratorInfo(mParamResonantFrequency, mParamQFactor, mParamMaxAmplitude); if (isParameterValid(vibratorInfo, mDescriptor)) { ASSERT_NO_FATAL_FAILURE(setAndVerifyParameter(createVibratorParam(vibratorInfo), HapticGenerator::vibratorInfo)); } else { ASSERT_NO_FATAL_FAILURE(setAndVerifyParameter(createVibratorParam(vibratorInfo), HapticGenerator::vibratorInfo, EX_ILLEGAL_ARGUMENT)); } } GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(HapticGeneratorVibratorInfoParamTest); INSTANTIATE_TEST_SUITE_P( HapticGeneratorValidTest, HapticGeneratorVibratorInfoParamTest, ::testing::Combine(testing::ValuesIn(EffectFactoryHelper::getAllEffectDescriptors( IFactory::descriptor, getEffectTypeUuidHapticGenerator())), testing::ValuesIn(kResonantFrequencyValues), testing::ValuesIn(kQFactorValues), testing::ValuesIn(kMaxAmplitude)), [](const testing::TestParamInfo& info) { auto descriptor = std::get(info.param).second; std::string resonantFrequency = std::to_string(std::get(info.param)); std::string qFactor = std::to_string(std::get(info.param)); std::string maxAmplitude = std::to_string(std::get(info.param)); std::string name = getPrefix(descriptor) + "_resonantFrequency" + resonantFrequency + "_qFactor" + qFactor + "_maxAmplitude" + maxAmplitude; std::replace_if( name.begin(), name.end(), [](const char c) { return !std::isalnum(c); }, '_'); return name; }); /** * The data tests do the following * -Generate test input. * -Check if the parameters are supported. Skip the unsupported parameter values. * -Validate increase in haptic output energy energy. **/ enum DataTestParam { EFFECT_INSTANCE, LAYOUT }; using HapticGeneratorDataTestParam = std::tuple; class HapticGeneratorDataTest : public ::testing::TestWithParam, public HapticGeneratorHelper { public: HapticGeneratorDataTest() : mChMask(std::get(GetParam())) { std::tie(mFactory, mDescriptor) = std::get(GetParam()); mAudioChannelCount = getChannelCount(AudioChannelLayout::make(mChMask), ~AudioChannelLayout::LAYOUT_HAPTIC_AB); mHapticChannelCount = getChannelCount(AudioChannelLayout::make(mChMask), AudioChannelLayout::LAYOUT_HAPTIC_AB); mAudioSamples = kFrameCount * mAudioChannelCount; mHapticSamples = kFrameCount * mHapticChannelCount; mInput.resize(mHapticSamples + mAudioSamples, 0); mOutput.resize(mHapticSamples + mAudioSamples, 0); } void SetUp() override { ASSERT_NO_FATAL_FAILURE(SetUpHapticGenerator(mChMask)); } void TearDown() override { ASSERT_NO_FATAL_FAILURE(TearDownHapticGenerator()); } void generateSinePeriod() { size_t cycleSize = kSamplingFrequency / kInputFrequency; size_t startSize = 0; while (startSize < mAudioSamples) { for (size_t i = 0; i < cycleSize; i++) { mInput[i + startSize] = sin(2 * M_PI * kInputFrequency * i / kSamplingFrequency); } startSize += mAudioSamples / 4; } } void setBaseVibratorParam() { auto vibratorInfo = createVibratorInfo(kDefaultResonantFrequency, kDefaultQfactor, kDefaultMaxAmp); if (isParameterValid(vibratorInfo, mDescriptor)) { ASSERT_NO_FATAL_FAILURE(setAndVerifyParameter(createVibratorParam(vibratorInfo), HapticGenerator::vibratorInfo)); } else { GTEST_SKIP() << "Invalid base vibrator values, skipping the test\n"; } } void setBaseScaleParam() { ASSERT_NO_FATAL_FAILURE(setAndVerifyParameter( createScaleParam({HapticGenerator::HapticScale(kDefaultScaleID, kDefaultScale)}), HapticGenerator::hapticScales)); } void validateIncreasingEnergy(HapticGenerator::Tag tag) { float baseEnergy = -1; for (auto param : mHapticParam) { ASSERT_NO_FATAL_FAILURE(setAndVerifyParameter(param, tag)); SCOPED_TRACE("Param: " + param.toString()); ASSERT_NO_FATAL_FAILURE(processAndWriteToOutput(mInput, mOutput, mEffect, &ret)); float hapticOutputEnergy = audio_utils_compute_energy_mono( mOutput.data() + mAudioSamples, AUDIO_FORMAT_PCM_FLOAT, mHapticSamples); EXPECT_GT(hapticOutputEnergy, baseEnergy); baseEnergy = hapticOutputEnergy; } } float findAbsMax(auto begin, auto end) { return *std::max_element(begin, end, [](float a, float b) { return std::abs(a) < std::abs(b); }); } void findMaxAmplitude() { for (float amp = 0.1; amp <= 1; amp += 0.1) { auto vibratorInfo = createVibratorInfo(kDefaultResonantFrequency, kDefaultQfactor, amp); if (!isParameterValid(vibratorInfo, mDescriptor)) { continue; } ASSERT_NO_FATAL_FAILURE(setAndVerifyParameter(createVibratorParam(vibratorInfo), HapticGenerator::vibratorInfo)); ASSERT_NO_FATAL_FAILURE(processAndWriteToOutput(mInput, mOutput, mEffect, &ret)); float outAmplitude = findAbsMax(mOutput.begin() + mAudioSamples, mOutput.end()); if (outAmplitude > mMaxAmplitude) { mMaxAmplitude = outAmplitude; } else { break; } } } const int kInputFrequency = 1000; float mMaxAmplitude = 0; size_t mHapticSamples; int32_t mChMask; int32_t mAudioChannelCount; int32_t mHapticChannelCount; size_t mAudioSamples; float mBaseHapticOutputEnergy; std::vector mHapticParam; // both input and output buffer includes audio and haptic samples std::vector mInput; std::vector mOutput; }; TEST_P(HapticGeneratorDataTest, IncreasingVibratorScaleTest) { generateInput(mInput, kInputFrequency, kSamplingFrequency, mAudioSamples); ASSERT_NO_FATAL_FAILURE(setBaseVibratorParam()); for (HapticGenerator::VibratorScale scale : kScaleValues) { mHapticParam.push_back( createScaleParam({HapticGenerator::HapticScale(kDefaultScaleID, scale)})); } ASSERT_NO_FATAL_FAILURE(validateIncreasingEnergy(HapticGenerator::hapticScales)); } TEST_P(HapticGeneratorDataTest, IncreasingMaxAmplitudeTest) { generateInput(mInput, kInputFrequency, kSamplingFrequency, mAudioSamples); ASSERT_NO_FATAL_FAILURE(setBaseScaleParam()); findMaxAmplitude(); std::vector increasingAmplitudeValues = {0.25f * mMaxAmplitude, 0.5f * mMaxAmplitude, 0.75f * mMaxAmplitude, mMaxAmplitude}; for (float amplitude : increasingAmplitudeValues) { auto vibratorInfo = createVibratorInfo(kDefaultResonantFrequency, kDefaultQfactor, amplitude); if (!isParameterValid(vibratorInfo, mDescriptor)) { continue; } mHapticParam.push_back(createVibratorParam(vibratorInfo)); } ASSERT_NO_FATAL_FAILURE(validateIncreasingEnergy(HapticGenerator::vibratorInfo)); } TEST_P(HapticGeneratorDataTest, DescreasingResonantFrequencyTest) { std::vector descreasingResonantFrequency = {800, 600, 400, 200}; generateInput(mInput, kInputFrequency, kSamplingFrequency, mAudioSamples); ASSERT_NO_FATAL_FAILURE(setBaseScaleParam()); for (float resonantFrequency : descreasingResonantFrequency) { auto vibratorInfo = createVibratorInfo(resonantFrequency, kDefaultQfactor, kDefaultMaxAmp); if (!isParameterValid(vibratorInfo, mDescriptor)) { continue; } mHapticParam.push_back(createVibratorParam(vibratorInfo)); } ASSERT_NO_FATAL_FAILURE(validateIncreasingEnergy(HapticGenerator::vibratorInfo)); } TEST_P(HapticGeneratorDataTest, IncreasingQfactorTest) { std::vector increasingQfactor = {16, 24, 32, 40}; generateSinePeriod(); ASSERT_NO_FATAL_FAILURE(setBaseScaleParam()); for (float qFactor : increasingQfactor) { auto vibratorInfo = createVibratorInfo(kDefaultResonantFrequency, qFactor, kDefaultMaxAmp); if (!isParameterValid(vibratorInfo, mDescriptor)) { continue; } mHapticParam.push_back(createVibratorParam(vibratorInfo)); } ASSERT_NO_FATAL_FAILURE(validateIncreasingEnergy(HapticGenerator::vibratorInfo)); } INSTANTIATE_TEST_SUITE_P( DataTest, HapticGeneratorDataTest, ::testing::Combine(testing::ValuesIn(EffectFactoryHelper::getAllEffectDescriptors( IFactory::descriptor, getEffectTypeUuidHapticGenerator())), testing::ValuesIn(kHapticOutputLayouts)), [](const testing::TestParamInfo& info) { auto descriptor = std::get(info.param).second; std::string layout = "0x" + std::format("{:x}", std::get(info.param)); std::string name = getPrefix(descriptor) + "_layout_" + layout; return name; }); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(HapticGeneratorDataTest); int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer()); ABinderProcess_setThreadPoolMaxThreadCount(1); ABinderProcess_startThreadPool(); return RUN_ALL_TESTS(); }