diff --git a/audio/aidl/vts/Android.bp b/audio/aidl/vts/Android.bp index 5218fdd014..d219fa41c3 100644 --- a/audio/aidl/vts/Android.bp +++ b/audio/aidl/vts/Android.bp @@ -26,6 +26,7 @@ cc_defaults { "android.hardware.common.fmq-V1-ndk", "libaudioaidlcommon", "libaidlcommonsupport", + "libpffft", ], header_libs: [ "libaudioaidl_headers", @@ -36,6 +37,7 @@ cc_defaults { "-Wextra", "-Werror", "-Wthread-safety", + "-Wno-error=unused-parameter", ], test_config_template: "VtsHalAudioTargetTestTemplate.xml", test_suites: [ diff --git a/audio/aidl/vts/EffectHelper.h b/audio/aidl/vts/EffectHelper.h index 0be4e5094f..82a07fdc10 100644 --- a/audio/aidl/vts/EffectHelper.h +++ b/audio/aidl/vts/EffectHelper.h @@ -37,6 +37,7 @@ #include "EffectFactoryHelper.h" #include "TestUtils.h" +#include "pffft.hpp" using namespace android; using aidl::android::hardware::audio::effect::CommandId; @@ -329,4 +330,45 @@ class EffectHelper { ASSERT_NO_FATAL_FAILURE(command(mEffect, CommandId::RESET)); ASSERT_NO_FATAL_FAILURE(expectState(mEffect, State::IDLE)); } + + // Find FFT bin indices for testFrequencies and get bin center frequencies + void roundToFreqCenteredToFftBin(std::vector& testFrequencies, + std::vector& binOffsets, const float kBinWidth) { + 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, + const int samplingFrequency) { + for (size_t i = 0; i < input.size(); i++) { + input[i] = 0; + + for (size_t j = 0; j < testFrequencies.size(); j++) { + input[i] += sin(2 * M_PI * testFrequencies[j] * i / samplingFrequency); + } + 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, const int nPointFFT) { + std::vector fftInput(nPointFFT); + PFFFT_Setup* inputHandle = pffft_new_setup(nPointFFT, 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; + } }; diff --git a/audio/aidl/vts/VtsHalVolumeTargetTest.cpp b/audio/aidl/vts/VtsHalVolumeTargetTest.cpp index aa2c05f61b..059d6ab984 100644 --- a/audio/aidl/vts/VtsHalVolumeTargetTest.cpp +++ b/audio/aidl/vts/VtsHalVolumeTargetTest.cpp @@ -21,6 +21,7 @@ using namespace android; +using aidl::android::hardware::audio::common::getChannelCount; using aidl::android::hardware::audio::effect::Descriptor; using aidl::android::hardware::audio::effect::getEffectTypeUuidVolume; using aidl::android::hardware::audio::effect::IEffect; @@ -29,6 +30,80 @@ using aidl::android::hardware::audio::effect::Parameter; using aidl::android::hardware::audio::effect::Volume; using android::hardware::audio::common::testing::detail::TestExecutionTracer; +class VolumeControlHelper : public EffectHelper { + public: + void SetUpVolumeControl() { + ASSERT_NE(nullptr, mFactory); + ASSERT_NO_FATAL_FAILURE(create(mFactory, mEffect, mDescriptor)); + initFrameCount(); + Parameter::Specific specific = getDefaultParamSpecific(); + Parameter::Common common = EffectHelper::createParamCommon( + 0 /* session */, 1 /* ioHandle */, kSamplingFrequency /* iSampleRate */, + kSamplingFrequency /* oSampleRate */, mInputFrameCount /* iFrameCount */, + mInputFrameCount /* oFrameCount */); + ASSERT_NO_FATAL_FAILURE(open(mEffect, common, specific, &mOpenEffectReturn, EX_NONE)); + ASSERT_NE(nullptr, mEffect); + } + + void TearDownVolumeControl() { + ASSERT_NO_FATAL_FAILURE(close(mEffect)); + ASSERT_NO_FATAL_FAILURE(destroy(mFactory, mEffect)); + mOpenEffectReturn = IEffect::OpenEffectReturn{}; + } + + Parameter::Specific getDefaultParamSpecific() { + Volume vol = Volume::make(kMinLevel); + Parameter::Specific specific = Parameter::Specific::make(vol); + return specific; + } + + Parameter createVolumeParam(int param, Volume::Tag volTag) { + return Parameter::make( + Parameter::Specific::make( + (volTag == Volume::mute) ? Volume::make(param) + : Volume::make(param))); + } + + void initFrameCount() { + int channelCount = getChannelCount( + AudioChannelLayout::make(kDefaultChannelLayout)); + mInputFrameCount = kBufferSize / channelCount; + mOutputFrameCount = kBufferSize / channelCount; + } + + bool isLevelValid(int level) { + auto vol = Volume::make(level); + return isParameterValid(vol, mDescriptor); + } + + void setAndVerifyParameters(Volume::Tag volTag, int param, binder_exception_t expected) { + auto expectedParam = createVolumeParam(param, volTag); + EXPECT_STATUS(expected, mEffect->setParameter(expectedParam)) << expectedParam.toString(); + + if (expected == EX_NONE) { + Volume::Id volId = Volume::Id::make(volTag); + + auto id = Parameter::Id::make(volId); + // 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(); + } + } + + static constexpr int kSamplingFrequency = 44100; + static constexpr int kDurationMilliSec = 2000; + static constexpr int kBufferSize = kSamplingFrequency * kDurationMilliSec / 1000; + static constexpr int kMinLevel = -96; + static constexpr int kDefaultChannelLayout = AudioChannelLayout::LAYOUT_STEREO; + long mInputFrameCount, mOutputFrameCount; + std::shared_ptr mFactory; + std::shared_ptr mEffect; + IEffect::OpenEffectReturn mOpenEffectReturn; + Descriptor mDescriptor; +}; /** * Here we focus on specific parameter checking, general IEffect interfaces testing performed in * VtsAudioEffectTargetTest. @@ -37,7 +112,8 @@ enum ParamName { PARAM_INSTANCE_NAME, PARAM_LEVEL, PARAM_MUTE }; using VolumeParamTestParam = std::tuple, Descriptor>, int, bool>; -class VolumeParamTest : public ::testing::TestWithParam, public EffectHelper { +class VolumeParamTest : public ::testing::TestWithParam, + public VolumeControlHelper { public: VolumeParamTest() : mParamLevel(std::get(GetParam())), @@ -45,94 +121,167 @@ class VolumeParamTest : public ::testing::TestWithParam, p std::tie(mFactory, mDescriptor) = std::get(GetParam()); } - void SetUp() override { - ASSERT_NE(nullptr, mFactory); - ASSERT_NO_FATAL_FAILURE(create(mFactory, mEffect, mDescriptor)); + void SetUp() override { ASSERT_NO_FATAL_FAILURE(SetUpVolumeControl()); } + void TearDown() override { TearDownVolumeControl(); } - 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)); - ASSERT_NE(nullptr, mEffect); - } - void TearDown() override { - ASSERT_NO_FATAL_FAILURE(close(mEffect)); - ASSERT_NO_FATAL_FAILURE(destroy(mFactory, mEffect)); - } - - Parameter::Specific getDefaultParamSpecific() { - Volume vol = Volume::make(-9600); - Parameter::Specific specific = Parameter::Specific::make(vol); - return specific; - } - - static const long kInputFrameCount = 0x100, kOutputFrameCount = 0x100; - std::shared_ptr mFactory; - std::shared_ptr mEffect; - Descriptor mDescriptor; int mParamLevel = 0; bool mParamMute = false; - - void SetAndGetParameters() { - for (auto& it : mTags) { - auto& tag = it.first; - auto& vol = it.second; - - // 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; - - // set parameter - Parameter expectParam; - Parameter::Specific specific; - specific.set(vol); - expectParam.set(specific); - EXPECT_STATUS(expected, mEffect->setParameter(expectParam)) << expectParam.toString(); - - // only get if parameter is in range and set success - if (expected == EX_NONE) { - Parameter getParam; - Parameter::Id id; - Volume::Id volId; - volId.set(tag); - id.set(volId); - EXPECT_STATUS(EX_NONE, mEffect->getParameter(id, &getParam)); - - EXPECT_EQ(expectParam, getParam) << "\nexpect:" << expectParam.toString() - << "\ngetParam:" << getParam.toString(); - } - } - } - - void addLevelParam(int level) { - Volume vol; - vol.set(level); - mTags.push_back({Volume::levelDb, vol}); - } - - void addMuteParam(bool mute) { - Volume vol; - vol.set(mute); - mTags.push_back({Volume::mute, vol}); - } - - private: - std::vector> mTags; - void CleanUp() { mTags.clear(); } }; -TEST_P(VolumeParamTest, SetAndGetLevel) { - EXPECT_NO_FATAL_FAILURE(addLevelParam(mParamLevel)); - SetAndGetParameters(); +TEST_P(VolumeParamTest, SetAndGetParams) { + ASSERT_NO_FATAL_FAILURE( + setAndVerifyParameters(Volume::levelDb, mParamLevel, + isLevelValid(mParamLevel) ? EX_NONE : EX_ILLEGAL_ARGUMENT)); + ASSERT_NO_FATAL_FAILURE(setAndVerifyParameters(Volume::mute, mParamMute, EX_NONE)); } -TEST_P(VolumeParamTest, SetAndGetMute) { - EXPECT_NO_FATAL_FAILURE(addMuteParam(mParamMute)); - SetAndGetParameters(); +using VolumeDataTestParam = std::pair, Descriptor>; + +class VolumeDataTest : public ::testing::TestWithParam, + public VolumeControlHelper { + public: + VolumeDataTest() { + std::tie(mFactory, mDescriptor) = GetParam(); + mInput.resize(kBufferSize); + mInputMag.resize(mTestFrequencies.size()); + mBinOffsets.resize(mTestFrequencies.size()); + roundToFreqCenteredToFftBin(mTestFrequencies, mBinOffsets, kBinWidth); + generateMultiTone(mTestFrequencies, mInput, kSamplingFrequency); + mInputMag = calculateMagnitude(mInput, mBinOffsets, kNPointFFT); + } + + std::vector calculatePercentageDiff(const std::vector& outputMag) { + std::vector percentages(mTestFrequencies.size()); + + for (size_t i = 0; i < mInputMag.size(); i++) { + float diff = mInputMag[i] - outputMag[i]; + percentages[i] = std::round(diff / mInputMag[i] * 100); + } + return percentages; + } + + // Convert Decibel value to Percentage + int percentageDb(float level) { return std::round((1 - (pow(10, level / 20))) * 100); } + + void SetUp() override { ASSERT_NO_FATAL_FAILURE(SetUpVolumeControl()); } + void TearDown() override { TearDownVolumeControl(); } + + static constexpr int kMaxAudioSample = 1; + static constexpr int kTransitionDuration = 300; + static constexpr int kNPointFFT = 32768; + static constexpr float kBinWidth = (float)kSamplingFrequency / kNPointFFT; + static constexpr size_t offset = kSamplingFrequency * kTransitionDuration / 1000; + static constexpr float kBaseLevel = 0; + std::vector mTestFrequencies = {100, 1000}; + std::vector mInput; + std::vector mInputMag; + std::vector mBinOffsets; +}; + +TEST_P(VolumeDataTest, ApplyLevelMuteUnmute) { + std::vector output(kBufferSize); + std::vector diffs(mTestFrequencies.size()); + std::vector outputMag(mTestFrequencies.size()); + + if (!isLevelValid(kBaseLevel)) { + GTEST_SKIP() << "Volume Level not supported, skipping the test\n"; + } + + // Apply Volume Level + + ASSERT_NO_FATAL_FAILURE(setAndVerifyParameters(Volume::levelDb, kBaseLevel, EX_NONE)); + ASSERT_NO_FATAL_FAILURE(processAndWriteToOutput(mInput, output, mEffect, &mOpenEffectReturn)); + + outputMag = calculateMagnitude(output, mBinOffsets, kNPointFFT); + diffs = calculatePercentageDiff(outputMag); + + for (size_t i = 0; i < diffs.size(); i++) { + ASSERT_EQ(diffs[i], percentageDb(kBaseLevel)); + } + + // Apply Mute + + ASSERT_NO_FATAL_FAILURE(setAndVerifyParameters(Volume::mute, true /*mute*/, EX_NONE)); + ASSERT_NO_FATAL_FAILURE(processAndWriteToOutput(mInput, output, mEffect, &mOpenEffectReturn)); + + std::vector subOutputMute(output.begin() + offset, output.end()); + outputMag = calculateMagnitude(subOutputMute, mBinOffsets, kNPointFFT); + diffs = calculatePercentageDiff(outputMag); + + for (size_t i = 0; i < diffs.size(); i++) { + ASSERT_EQ(diffs[i], percentageDb(kMinLevel /*Mute*/)); + } + + // Verifying Fade out + outputMag = calculateMagnitude(output, mBinOffsets, kNPointFFT); + diffs = calculatePercentageDiff(outputMag); + + for (size_t i = 0; i < diffs.size(); i++) { + ASSERT_LT(diffs[i], percentageDb(kMinLevel /*Mute*/)); + } + + // Apply Unmute + + ASSERT_NO_FATAL_FAILURE(setAndVerifyParameters(Volume::mute, false /*unmute*/, EX_NONE)); + ASSERT_NO_FATAL_FAILURE(processAndWriteToOutput(mInput, output, mEffect, &mOpenEffectReturn)); + + std::vector subOutputUnmute(output.begin() + offset, output.end()); + + outputMag = calculateMagnitude(subOutputUnmute, mBinOffsets, kNPointFFT); + diffs = calculatePercentageDiff(outputMag); + + for (size_t i = 0; i < diffs.size(); i++) { + ASSERT_EQ(diffs[i], percentageDb(kBaseLevel)); + } + + // Verifying Fade in + outputMag = calculateMagnitude(output, mBinOffsets, kNPointFFT); + diffs = calculatePercentageDiff(outputMag); + + for (size_t i = 0; i < diffs.size(); i++) { + ASSERT_GT(diffs[i], percentageDb(kBaseLevel)); + } +} + +TEST_P(VolumeDataTest, DecreasingLevels) { + std::vector decreasingLevels = {-24, -48, -96}; + std::vector baseOutput(kBufferSize); + std::vector baseDiffs(mTestFrequencies.size()); + std::vector outputMag(mTestFrequencies.size()); + + if (!isLevelValid(kBaseLevel)) { + GTEST_SKIP() << "Volume Level not supported, skipping the test\n"; + } + + ASSERT_NO_FATAL_FAILURE(setAndVerifyParameters(Volume::levelDb, kBaseLevel, EX_NONE)); + ASSERT_NO_FATAL_FAILURE( + processAndWriteToOutput(mInput, baseOutput, mEffect, &mOpenEffectReturn)); + + outputMag = calculateMagnitude(baseOutput, mBinOffsets, kNPointFFT); + baseDiffs = calculatePercentageDiff(outputMag); + + for (int level : decreasingLevels) { + std::vector output(kBufferSize); + std::vector diffs(mTestFrequencies.size()); + + // Skipping the further steps for unnsupported level values + if (!isLevelValid(level)) { + continue; + } + ASSERT_NO_FATAL_FAILURE(setAndVerifyParameters(Volume::levelDb, level, EX_NONE)); + ASSERT_NO_FATAL_FAILURE( + processAndWriteToOutput(mInput, output, mEffect, &mOpenEffectReturn)); + + outputMag = calculateMagnitude(output, mBinOffsets, kNPointFFT); + diffs = calculatePercentageDiff(outputMag); + + // Decrease in volume level results in greater magnitude difference + for (size_t i = 0; i < diffs.size(); i++) { + ASSERT_GT(diffs[i], baseDiffs[i]); + } + + baseDiffs = diffs; + } } std::vector, Descriptor>> kDescPair; @@ -157,6 +306,20 @@ INSTANTIATE_TEST_SUITE_P( GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VolumeParamTest); +INSTANTIATE_TEST_SUITE_P(VolumeTest, VolumeDataTest, + testing::ValuesIn(EffectFactoryHelper::getAllEffectDescriptors( + IFactory::descriptor, getEffectTypeUuidVolume())), + [](const testing::TestParamInfo& info) { + auto descriptor = info.param; + std::string name = getPrefix(descriptor.second); + std::replace_if( + name.begin(), name.end(), + [](const char c) { return !std::isalnum(c); }, '_'); + return name; + }); + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VolumeDataTest); + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());