blob: 1b0b681c5c03198859a4b87cc5ddfd4843207ffc [file] [log] [blame] [edit]
/*
* 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 <map>
#include <utility>
#include <vector>
#define LOG_TAG "VtsHalHapticGeneratorTargetTest"
#include <android-base/logging.h>
#include <android/binder_enums.h>
#include <audio_utils/power.h>
#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<int>::min();
const int MAX_ID = std::numeric_limits<int>::max();
const float MIN_FLOAT = std::numeric_limits<float>::min();
const float MAX_FLOAT = std::numeric_limits<float>::max();
std::vector<HapticGenerator::VibratorScale> kScaleValues = {
ndk::enum_range<HapticGenerator::VibratorScale>().begin(),
ndk::enum_range<HapticGenerator::VibratorScale>().end()};
const std::vector<float> kScaleFactorValues = {HapticGenerator::HapticScale::UNDEFINED_SCALE_FACTOR,
0.0f, 0.5f, 1.0f, MAX_FLOAT};
const std::vector<float> kAdaptiveScaleFactorValues = {
HapticGenerator::HapticScale::UNDEFINED_SCALE_FACTOR, 0.0f, 0.5f, 1.0f, MAX_FLOAT};
const std::vector<float> kResonantFrequencyValues = {MIN_FLOAT, 100, MAX_FLOAT};
const std::vector<float> kQFactorValues = {MIN_FLOAT, 100, MAX_FLOAT};
const std::vector<float> kMaxAmplitude = {MIN_FLOAT, 100, MAX_FLOAT};
constexpr int HAPTIC_SCALE_FACTORS_EFFECT_MIN_VERSION = 3;
static const std::vector<int32_t> 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<AudioChannelLayout::layoutMask>(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<HapticGenerator::HapticScale>& hapticScales) {
return Parameter::make<Parameter::specific>(
Parameter::Specific::make<Parameter::Specific::hapticGenerator>(
HapticGenerator::make<HapticGenerator::hapticScales>(hapticScales)));
}
Parameter createVibratorParam(HapticGenerator::VibratorInformation vibrationInfo) {
return Parameter::make<Parameter::specific>(
Parameter::Specific::make<Parameter::Specific::hapticGenerator>(
HapticGenerator::make<HapticGenerator::vibratorInfo>(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<Parameter::Id::hapticGeneratorTag>(
HapticGenerator::Id::make<HapticGenerator::Id::commonTag>(
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<IFactory> mFactory;
std::shared_ptr<IEffect> 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<std::shared_ptr<IFactory>, Descriptor>;
class HapticGeneratorScaleParamTest : public ::testing::TestWithParam<EffectInstance>,
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<HapticGenerator::HapticScale> hapticScales;
for (int i = 0; i < static_cast<int>(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<HapticGenerator::HapticScale> hapticScales;
for (int i = 0; i < static_cast<int>(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<HapticGenerator::HapticScale> hapticScales;
for (int i = 0; i < static_cast<int>(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<HapticGeneratorScaleParamTest::ParamType>& 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<EffectInstance, float, float, float>;
class HapticGeneratorVibratorInfoParamTest
: public ::testing::TestWithParam<HapticGeneratorVibratorInfoTestParam>,
public HapticGeneratorHelper {
public:
HapticGeneratorVibratorInfoParamTest()
: mParamResonantFrequency(std::get<VIBRATOR_PARAM_RESONANT_FREQUENCY>(GetParam())),
mParamQFactor(std::get<VIBRATOR_PARAM_Q_FACTOR>(GetParam())),
mParamMaxAmplitude(std::get<VIBRATOR_PARAM_MAX_AMPLITUDE>(GetParam())) {
std::tie(mFactory, mDescriptor) = std::get<VIBRATOR_PARAM_INSTANCE>(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<HapticGenerator, Range::hapticGenerator>(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<HapticGeneratorVibratorInfoParamTest::ParamType>& info) {
auto descriptor = std::get<VIBRATOR_PARAM_INSTANCE>(info.param).second;
std::string resonantFrequency =
std::to_string(std::get<VIBRATOR_PARAM_RESONANT_FREQUENCY>(info.param));
std::string qFactor = std::to_string(std::get<VIBRATOR_PARAM_Q_FACTOR>(info.param));
std::string maxAmplitude =
std::to_string(std::get<VIBRATOR_PARAM_MAX_AMPLITUDE>(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<EffectInstance, int32_t>;
// minimal HAL interface version to run the data path test
constexpr int32_t kMinDataTestHalVersion = 3;
class HapticGeneratorDataTest : public ::testing::TestWithParam<HapticGeneratorDataTestParam>,
public HapticGeneratorHelper {
public:
HapticGeneratorDataTest() : mChMask(std::get<LAYOUT>(GetParam())) {
std::tie(mFactory, mDescriptor) = std::get<EFFECT_INSTANCE>(GetParam());
mAudioChannelCount =
getChannelCount(AudioChannelLayout::make<AudioChannelLayout::layoutMask>(mChMask),
~AudioChannelLayout::LAYOUT_HAPTIC_AB);
mHapticChannelCount =
getChannelCount(AudioChannelLayout::make<AudioChannelLayout::layoutMask>(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));
if (int32_t version;
mEffect->getInterfaceVersion(&version).isOk() && version < kMinDataTestHalVersion) {
GTEST_SKIP() << "Skipping the data test for version: " << version << "\n";
}
}
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<HapticGenerator, Range::hapticGenerator>(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<HapticGenerator, Range::hapticGenerator>(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<Parameter> mHapticParam;
// both input and output buffer includes audio and haptic samples
std::vector<float> mInput;
std::vector<float> 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<float> increasingAmplitudeValues = {0.25f * mMaxAmplitude, 0.5f * mMaxAmplitude,
0.75f * mMaxAmplitude, mMaxAmplitude};
for (float amplitude : increasingAmplitudeValues) {
auto vibratorInfo =
createVibratorInfo(kDefaultResonantFrequency, kDefaultQfactor, amplitude);
if (!isParameterValid<HapticGenerator, Range::hapticGenerator>(vibratorInfo, mDescriptor)) {
continue;
}
mHapticParam.push_back(createVibratorParam(vibratorInfo));
}
ASSERT_NO_FATAL_FAILURE(validateIncreasingEnergy(HapticGenerator::vibratorInfo));
}
TEST_P(HapticGeneratorDataTest, DescreasingResonantFrequencyTest) {
std::vector<float> 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<HapticGenerator, Range::hapticGenerator>(vibratorInfo, mDescriptor)) {
continue;
}
mHapticParam.push_back(createVibratorParam(vibratorInfo));
}
ASSERT_NO_FATAL_FAILURE(validateIncreasingEnergy(HapticGenerator::vibratorInfo));
}
TEST_P(HapticGeneratorDataTest, IncreasingQfactorTest) {
std::vector<float> increasingQfactor = {16, 24, 32, 40};
generateSinePeriod();
ASSERT_NO_FATAL_FAILURE(setBaseScaleParam());
for (float qFactor : increasingQfactor) {
auto vibratorInfo = createVibratorInfo(kDefaultResonantFrequency, qFactor, kDefaultMaxAmp);
if (!isParameterValid<HapticGenerator, Range::hapticGenerator>(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<HapticGeneratorDataTest::ParamType>& info) {
auto descriptor = std::get<EFFECT_INSTANCE>(info.param).second;
std::string layout = "0x" + std::format("{:x}", std::get<LAYOUT>(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();
}