diff --git a/audio/aidl/vts/Android.bp b/audio/aidl/vts/Android.bp index 85319ec1b0..106f85e445 100644 --- a/audio/aidl/vts/Android.bp +++ b/audio/aidl/vts/Android.bp @@ -90,6 +90,12 @@ cc_test { name: "VtsHalBassBoostTargetTest", defaults: ["VtsHalAudioEffectTargetTestDefaults"], srcs: ["VtsHalBassBoostTargetTest.cpp"], + cflags: [ + "-Wno-error=unused-parameter", + ], + static_libs: [ + "libpffft", + ], } cc_test { diff --git a/audio/aidl/vts/VtsHalBassBoostTargetTest.cpp b/audio/aidl/vts/VtsHalBassBoostTargetTest.cpp index 135940df9d..b54b44233f 100644 --- a/audio/aidl/vts/VtsHalBassBoostTargetTest.cpp +++ b/audio/aidl/vts/VtsHalBassBoostTargetTest.cpp @@ -14,13 +14,17 @@ * limitations under the License. */ -#define LOG_TAG "VtsHalBassBoostTest" -#include +#include +#define LOG_TAG "VtsHalBassBoostTest" +#include +#include #include "EffectHelper.h" +#include "pffft.hpp" using namespace android; +using aidl::android::hardware::audio::common::getChannelCount; using aidl::android::hardware::audio::effect::BassBoost; using aidl::android::hardware::audio::effect::Capability; using aidl::android::hardware::audio::effect::Descriptor; @@ -30,13 +34,11 @@ using aidl::android::hardware::audio::effect::IFactory; using aidl::android::hardware::audio::effect::Parameter; using aidl::android::hardware::audio::effect::Range; using android::hardware::audio::common::testing::detail::TestExecutionTracer; -/** - * Here we focus on specific parameter checking, general IEffect interfaces testing performed in - * VtsAudioEffectTargetTest. - */ -enum ParamName { PARAM_INSTANCE_NAME, PARAM_STRENGTH }; -using BassBoostParamTestParam = std::tuple, Descriptor>, int>; +// minimal HAL interface version to run bassboost data path test +constexpr int32_t kMinDataTestHalVersion = 2; +static const std::vector kLayouts = {AudioChannelLayout::LAYOUT_STEREO, + AudioChannelLayout::LAYOUT_MONO}; /* * Testing parameter range, assuming the parameter supported by effect is in this range. * Parameter should be within the valid range defined in the documentation, @@ -44,29 +46,29 @@ using BassBoostParamTestParam = std::tuple, * otherwise expect EX_ILLEGAL_ARGUMENT. */ -class BassBoostParamTest : public ::testing::TestWithParam, - public EffectHelper { +class BassBoostEffectHelper : public EffectHelper { public: - BassBoostParamTest() : mParamStrength(std::get(GetParam())) { - std::tie(mFactory, mDescriptor) = std::get(GetParam()); - } - - void SetUp() override { + void SetUpBassBoost(int32_t layout = AudioChannelLayout::LAYOUT_STEREO) { ASSERT_NE(nullptr, mFactory); ASSERT_NO_FATAL_FAILURE(create(mFactory, mEffect, mDescriptor)); + setFrameCounts(layout); + + AudioChannelLayout channelLayout = + AudioChannelLayout::make(layout); Parameter::Specific specific = getDefaultParamSpecific(); Parameter::Common common = EffectHelper::createParamCommon( - 0 /* session */, 1 /* ioHandle */, 44100 /* iSampleRate */, 44100 /* oSampleRate */, - kInputFrameCount /* iFrameCount */, kOutputFrameCount /* oFrameCount */); - IEffect::OpenEffectReturn ret; - ASSERT_NO_FATAL_FAILURE(open(mEffect, common, specific, &ret, EX_NONE)); + 0 /* session */, 1 /* ioHandle */, kSamplingFrequency /* iSampleRate */, + kSamplingFrequency /* oSampleRate */, mInputFrameCount /* iFrameCount */, + mOutputFrameCount /* oFrameCount */, channelLayout, channelLayout); + ASSERT_NO_FATAL_FAILURE(open(mEffect, common, specific, &mOpenEffectReturn, EX_NONE)); ASSERT_NE(nullptr, mEffect); } - void TearDown() override { + void TearDownBassBoost() { ASSERT_NO_FATAL_FAILURE(close(mEffect)); ASSERT_NO_FATAL_FAILURE(destroy(mFactory, mEffect)); + mOpenEffectReturn = IEffect::OpenEffectReturn{}; } Parameter::Specific getDefaultParamSpecific() { @@ -76,58 +78,214 @@ class BassBoostParamTest : public ::testing::TestWithParam mFactory; - std::shared_ptr mEffect; - Descriptor mDescriptor; - int mParamStrength = 0; + void setFrameCounts(int32_t inputBufferLayout) { + int channelCount = getChannelCount( + AudioChannelLayout::make(inputBufferLayout)); + mInputFrameCount = kInputSize / channelCount; + mOutputFrameCount = kInputSize / channelCount; + } - void SetAndGetBassBoostParameters() { - for (auto& it : mTags) { - auto& tag = it.first; - auto& bb = it.second; + Parameter createBassBoostParam(int strength) { + return Parameter::make( + Parameter::Specific::make( + BassBoost::make(strength))); + } - // validate parameter - Descriptor desc; - ASSERT_STATUS(EX_NONE, mEffect->getDescriptor(&desc)); - const bool valid = isParameterValid(it.second, desc); - const binder_exception_t expected = valid ? EX_NONE : EX_ILLEGAL_ARGUMENT; + bool isStrengthValid(int strength) { + auto bb = BassBoost::make(strength); + return isParameterValid(bb, mDescriptor); + } - // set parameter - Parameter expectParam; - Parameter::Specific specific; - specific.set(bb); - expectParam.set(specific); - EXPECT_STATUS(expected, mEffect->setParameter(expectParam)) << expectParam.toString(); + void setAndVerifyParameters(int strength, binder_exception_t expected) { + auto expectedParam = createBassBoostParam(strength); + EXPECT_STATUS(expected, mEffect->setParameter(expectedParam)) << expectedParam.toString(); - // only get if parameter in range and set success - if (expected == EX_NONE) { - Parameter getParam; - Parameter::Id id; - BassBoost::Id bbId; - bbId.set(tag); - id.set(bbId); - // if set success, then get should match - EXPECT_STATUS(expected, mEffect->getParameter(id, &getParam)); - EXPECT_EQ(expectParam, getParam); - } + if (expected == EX_NONE) { + auto bbId = BassBoost::Id::make( + BassBoost::Tag(BassBoost::strengthPm)); + auto id = Parameter::Id::make(bbId); + // get parameter + Parameter getParam; + // if set success, then get should match + EXPECT_STATUS(expected, mEffect->getParameter(id, &getParam)); + EXPECT_EQ(expectedParam, getParam) << "\nexpectedParam:" << expectedParam.toString() + << "\ngetParam:" << getParam.toString(); } } - void addStrengthParam(int strength) { - BassBoost bb; - bb.set(strength); - mTags.push_back({BassBoost::strengthPm, bb}); + static constexpr int kSamplingFrequency = 44100; + static constexpr int kDurationMilliSec = 2000; + static constexpr int kInputSize = kSamplingFrequency * kDurationMilliSec / 1000; + long mInputFrameCount, mOutputFrameCount; + std::shared_ptr mFactory; + Descriptor mDescriptor; + std::shared_ptr mEffect; + IEffect::OpenEffectReturn mOpenEffectReturn; +}; + +/** + * Here we focus on specific parameter checking, general IEffect interfaces testing performed in + * VtsAudioEffectTargetTest. + */ +enum ParamName { PARAM_INSTANCE_NAME, PARAM_STRENGTH }; +using BassBoostParamTestParam = std::tuple, Descriptor>, int>; + +class BassBoostParamTest : public ::testing::TestWithParam, + public BassBoostEffectHelper { + public: + BassBoostParamTest() : mParamStrength(std::get(GetParam())) { + std::tie(mFactory, mDescriptor) = std::get(GetParam()); } - private: - std::vector> mTags; - void CleanUp() { mTags.clear(); } + void SetUp() override { ASSERT_NO_FATAL_FAILURE(SetUpBassBoost()); } + void TearDown() override { TearDownBassBoost(); } + + int mParamStrength = 0; }; TEST_P(BassBoostParamTest, SetAndGetStrength) { - EXPECT_NO_FATAL_FAILURE(addStrengthParam(mParamStrength)); - SetAndGetBassBoostParameters(); + if (isStrengthValid(mParamStrength)) { + ASSERT_NO_FATAL_FAILURE(setAndVerifyParameters(mParamStrength, EX_NONE)); + } else { + ASSERT_NO_FATAL_FAILURE(setAndVerifyParameters(mParamStrength, EX_ILLEGAL_ARGUMENT)); + } +} + +enum DataParamName { DATA_INSTANCE_NAME, DATA_LAYOUT }; + +using BassBoostDataTestParam = + std::tuple, Descriptor>, int32_t>; + +class BassBoostDataTest : public ::testing::TestWithParam, + public BassBoostEffectHelper { + public: + BassBoostDataTest() : mChannelLayout(std::get(GetParam())) { + std::tie(mFactory, mDescriptor) = std::get(GetParam()); + mStrengthValues = getTestValueSet( + {std::get(GetParam())}, expandTestValueBasic); + } + + void SetUp() override { + ASSERT_NO_FATAL_FAILURE(SetUpBassBoost(mChannelLayout)); + if (int32_t version; + mEffect->getInterfaceVersion(&version).isOk() && version < kMinDataTestHalVersion) { + GTEST_SKIP() << "Skipping the data test for version: " << version << "\n"; + } + } + + void TearDown() override { TearDownBassBoost(); } + + // Find FFT bin indices for testFrequencies and get bin center frequencies + void roundToFreqCenteredToFftBin(std::vector& testFrequencies, + std::vector& binOffsets) { + for (size_t i = 0; i < testFrequencies.size(); i++) { + binOffsets[i] = std::round(testFrequencies[i] / kBinWidth); + testFrequencies[i] = std::round(binOffsets[i] * kBinWidth); + } + } + + // Generate multitone input between -1 to +1 using testFrequencies + void generateMultiTone(const std::vector& testFrequencies, std::vector& input) { + for (auto i = 0; i < kInputSize; i++) { + input[i] = 0; + + for (size_t j = 0; j < testFrequencies.size(); j++) { + input[i] += sin(2 * M_PI * testFrequencies[j] * i / kSamplingFrequency); + } + input[i] /= testFrequencies.size(); + } + } + + // Use FFT transform to convert the buffer to frequency domain + // Compute its magnitude at binOffsets + std::vector calculateMagnitude(const std::vector& buffer, + const std::vector& binOffsets) { + std::vector fftInput(kNPointFFT); + PFFFT_Setup* inputHandle = pffft_new_setup(kNPointFFT, PFFFT_REAL); + pffft_transform_ordered(inputHandle, buffer.data(), fftInput.data(), nullptr, + PFFFT_FORWARD); + pffft_destroy_setup(inputHandle); + std::vector bufferMag(binOffsets.size()); + for (size_t i = 0; i < binOffsets.size(); i++) { + size_t k = binOffsets[i]; + bufferMag[i] = sqrt((fftInput[k * 2] * fftInput[k * 2]) + + (fftInput[k * 2 + 1] * fftInput[k * 2 + 1])); + } + + return bufferMag; + } + + // Calculate gain difference between low frequency and high frequency magnitude + float calculateGainDiff(const std::vector& inputMag, + const std::vector& outputMag) { + std::vector gains(inputMag.size()); + + for (size_t i = 0; i < inputMag.size(); i++) { + gains[i] = 20 * log10(outputMag[i] / inputMag[i]); + } + + return gains[0] - gains[1]; + } + + static constexpr int kNPointFFT = 32768; + static constexpr float kBinWidth = (float)kSamplingFrequency / kNPointFFT; + std::set mStrengthValues; + int32_t mChannelLayout; +}; + +TEST_P(BassBoostDataTest, IncreasingStrength) { + // Frequencies to generate multitone input + std::vector testFrequencies = {100, 1000}; + + // FFT bin indices for testFrequencies + std::vector binOffsets(testFrequencies.size()); + + std::vector input(kInputSize); + std::vector baseOutput(kInputSize); + + std::vector inputMag(testFrequencies.size()); + float prevGain = -100; + + roundToFreqCenteredToFftBin(testFrequencies, binOffsets); + + generateMultiTone(testFrequencies, input); + + inputMag = calculateMagnitude(input, binOffsets); + + if (isStrengthValid(0)) { + ASSERT_NO_FATAL_FAILURE(setAndVerifyParameters(0, EX_NONE)); + } else { + GTEST_SKIP() << "Strength not supported, skipping the test\n"; + } + + ASSERT_NO_FATAL_FAILURE( + processAndWriteToOutput(input, baseOutput, mEffect, &mOpenEffectReturn)); + + std::vector baseMag(testFrequencies.size()); + baseMag = calculateMagnitude(baseOutput, binOffsets); + float baseDiff = calculateGainDiff(inputMag, baseMag); + + for (int strength : mStrengthValues) { + // Skipping the further steps for invalid strength values + if (!isStrengthValid(strength)) { + continue; + } + + ASSERT_NO_FATAL_FAILURE(setAndVerifyParameters(strength, EX_NONE)); + + std::vector output(kInputSize); + std::vector outputMag(testFrequencies.size()); + + ASSERT_NO_FATAL_FAILURE( + processAndWriteToOutput(input, output, mEffect, &mOpenEffectReturn)); + + outputMag = calculateMagnitude(output, binOffsets); + float diff = calculateGainDiff(inputMag, outputMag); + + ASSERT_GT(diff, prevGain); + ASSERT_GT(diff, baseDiff); + prevGain = diff; + } } std::vector, Descriptor>> kDescPair; @@ -150,6 +308,22 @@ INSTANTIATE_TEST_SUITE_P( GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BassBoostParamTest); +INSTANTIATE_TEST_SUITE_P( + BassBoostTest, BassBoostDataTest, + ::testing::Combine(testing::ValuesIn(EffectFactoryHelper::getAllEffectDescriptors( + IFactory::descriptor, getEffectTypeUuidBassBoost())), + testing::ValuesIn(kLayouts)), + [](const testing::TestParamInfo& info) { + auto descriptor = std::get(info.param).second; + std::string layout = std::to_string(std::get(info.param)); + std::string name = getPrefix(descriptor) + "_layout_" + layout; + std::replace_if( + name.begin(), name.end(), [](const char c) { return !std::isalnum(c); }, '_'); + return name; + }); + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BassBoostDataTest); + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());