Ram Mohan | 58263ad | 2022-11-15 21:26:37 +0530 | [diff] [blame] | 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 | |
Ram Mohan | 58263ad | 2022-11-15 21:26:37 +0530 | [diff] [blame] | 17 | #include <fstream> |
| 18 | #include <iostream> |
| 19 | #include <string> |
| 20 | #include <tuple> |
| 21 | #include <vector> |
| 22 | |
Mikhail Naganov | f84a558 | 2024-02-15 11:26:33 -0800 | [diff] [blame] | 23 | // #define LOG_NDEBUG 0 |
| 24 | #define LOG_TAG "AudioEffectAnalyser" |
| 25 | |
| 26 | #include <android-base/file.h> |
| 27 | #include <android-base/stringprintf.h> |
| 28 | #include <binder/ProcessState.h> |
| 29 | #include <gtest/gtest.h> |
| 30 | #include <media/AudioEffect.h> |
| 31 | #include <system/audio_effects/effect_bassboost.h> |
| 32 | #include <system/audio_effects/effect_equalizer.h> |
| 33 | |
Ram Mohan | 58263ad | 2022-11-15 21:26:37 +0530 | [diff] [blame] | 34 | #include "audio_test_utils.h" |
| 35 | #include "pffft.hpp" |
Mikhail Naganov | f84a558 | 2024-02-15 11:26:33 -0800 | [diff] [blame] | 36 | #include "test_execution_tracer.h" |
Ram Mohan | 58263ad | 2022-11-15 21:26:37 +0530 | [diff] [blame] | 37 | |
| 38 | #define CHECK_OK(expr, msg) \ |
| 39 | mStatus = (expr); \ |
| 40 | if (OK != mStatus) { \ |
| 41 | mMsg = (msg); \ |
| 42 | return; \ |
| 43 | } |
| 44 | |
| 45 | using namespace android; |
| 46 | |
| 47 | constexpr float kDefAmplitude = 0.60f; |
| 48 | |
| 49 | constexpr float kPlayBackDurationSec = 1.5; |
| 50 | constexpr float kCaptureDurationSec = 1.0; |
| 51 | constexpr float kPrimeDurationInSec = 0.5; |
| 52 | |
| 53 | // chosen to safely sample largest center freq of eq bands |
| 54 | constexpr uint32_t kSamplingFrequency = 48000; |
| 55 | |
| 56 | // allows no fmt conversion before fft |
| 57 | constexpr audio_format_t kFormat = AUDIO_FORMAT_PCM_FLOAT; |
| 58 | |
| 59 | // playback and capture are done with channel mask configured to mono. |
| 60 | // effect analysis should not depend on mask, mono makes it easier. |
| 61 | |
| 62 | constexpr int kNPointFFT = 16384; |
| 63 | constexpr float kBinWidth = (float)kSamplingFrequency / kNPointFFT; |
| 64 | |
| 65 | const char* gPackageName = "AudioEffectAnalyser"; |
| 66 | |
| 67 | static_assert(kPrimeDurationInSec + 2 * kNPointFFT / kSamplingFrequency < kCaptureDurationSec, |
| 68 | "capture at least, prime, pad, nPointFft size of samples"); |
| 69 | static_assert(kPrimeDurationInSec + 2 * kNPointFFT / kSamplingFrequency < kPlayBackDurationSec, |
| 70 | "playback needs to be active during capture"); |
| 71 | |
| 72 | struct CaptureEnv { |
| 73 | // input args |
| 74 | uint32_t mSampleRate{kSamplingFrequency}; |
| 75 | audio_format_t mFormat{kFormat}; |
| 76 | audio_channel_mask_t mChannelMask{AUDIO_CHANNEL_IN_MONO}; |
| 77 | float mCaptureDuration{kCaptureDurationSec}; |
| 78 | // output val |
| 79 | status_t mStatus{OK}; |
| 80 | std::string mMsg; |
| 81 | std::string mDumpFileName; |
| 82 | |
| 83 | ~CaptureEnv(); |
| 84 | void capture(); |
| 85 | }; |
| 86 | |
| 87 | CaptureEnv::~CaptureEnv() { |
| 88 | if (!mDumpFileName.empty()) { |
| 89 | std::ifstream f(mDumpFileName); |
| 90 | if (f.good()) { |
| 91 | f.close(); |
| 92 | remove(mDumpFileName.c_str()); |
| 93 | } |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | void CaptureEnv::capture() { |
| 98 | audio_port_v7 port; |
| 99 | CHECK_OK(getPortByAttributes(AUDIO_PORT_ROLE_SOURCE, AUDIO_PORT_TYPE_DEVICE, |
| 100 | AUDIO_DEVICE_IN_REMOTE_SUBMIX, "0", port), |
| 101 | "Could not find port") |
| 102 | const auto capture = |
| 103 | sp<AudioCapture>::make(AUDIO_SOURCE_REMOTE_SUBMIX, mSampleRate, mFormat, mChannelMask); |
| 104 | CHECK_OK(capture->create(), "record creation failed") |
| 105 | CHECK_OK(capture->setRecordDuration(mCaptureDuration), "set record duration failed") |
| 106 | CHECK_OK(capture->enableRecordDump(), "enable record dump failed") |
| 107 | auto cbCapture = sp<OnAudioDeviceUpdateNotifier>::make(); |
| 108 | CHECK_OK(capture->getAudioRecordHandle()->addAudioDeviceCallback(cbCapture), |
| 109 | "addAudioDeviceCallback failed") |
| 110 | CHECK_OK(capture->start(), "start recording failed") |
| 111 | CHECK_OK(capture->audioProcess(), "recording process failed") |
| 112 | CHECK_OK(cbCapture->waitForAudioDeviceCb(), "audio device callback notification timed out"); |
| 113 | if (port.id != capture->getAudioRecordHandle()->getRoutedDeviceId()) { |
| 114 | CHECK_OK(BAD_VALUE, "Capture NOT routed on expected port") |
| 115 | } |
| 116 | CHECK_OK(getPortByAttributes(AUDIO_PORT_ROLE_SINK, AUDIO_PORT_TYPE_DEVICE, |
| 117 | AUDIO_DEVICE_OUT_REMOTE_SUBMIX, "0", port), |
| 118 | "Could not find port") |
| 119 | CHECK_OK(capture->stop(), "record stop failed") |
| 120 | mDumpFileName = capture->getRecordDumpFileName(); |
| 121 | } |
| 122 | |
| 123 | struct PlaybackEnv { |
| 124 | // input args |
| 125 | uint32_t mSampleRate{kSamplingFrequency}; |
| 126 | audio_format_t mFormat{kFormat}; |
| 127 | audio_channel_mask_t mChannelMask{AUDIO_CHANNEL_OUT_MONO}; |
| 128 | audio_session_t mSessionId{AUDIO_SESSION_NONE}; |
| 129 | std::string mRes; |
| 130 | // output val |
| 131 | status_t mStatus{OK}; |
| 132 | std::string mMsg; |
| 133 | |
| 134 | void play(); |
| 135 | }; |
| 136 | |
| 137 | void PlaybackEnv::play() { |
| 138 | const auto ap = |
| 139 | sp<AudioPlayback>::make(mSampleRate, mFormat, mChannelMask, AUDIO_OUTPUT_FLAG_NONE, |
| 140 | mSessionId, AudioTrack::TRANSFER_OBTAIN); |
| 141 | CHECK_OK(ap->loadResource(mRes.c_str()), "Unable to open Resource") |
| 142 | const auto cbPlayback = sp<OnAudioDeviceUpdateNotifier>::make(); |
| 143 | CHECK_OK(ap->create(), "track creation failed") |
| 144 | ap->getAudioTrackHandle()->setVolume(1.0f); |
| 145 | CHECK_OK(ap->getAudioTrackHandle()->addAudioDeviceCallback(cbPlayback), |
| 146 | "addAudioDeviceCallback failed") |
| 147 | CHECK_OK(ap->start(), "audio track start failed") |
| 148 | CHECK_OK(cbPlayback->waitForAudioDeviceCb(), "audio device callback notification timed out") |
| 149 | CHECK_OK(ap->onProcess(), "playback process failed") |
| 150 | ap->stop(); |
| 151 | } |
| 152 | |
| 153 | void generateMultiTone(const std::vector<int>& toneFrequencies, float samplingFrequency, |
| 154 | float duration, float amplitude, float* buffer, int numSamples) { |
| 155 | int totalFrameCount = (samplingFrequency * duration); |
| 156 | int limit = std::min(totalFrameCount, numSamples); |
| 157 | |
| 158 | for (auto i = 0; i < limit; i++) { |
| 159 | buffer[i] = 0; |
| 160 | for (auto j = 0; j < toneFrequencies.size(); j++) { |
| 161 | buffer[i] += sin(2 * M_PI * toneFrequencies[j] * i / samplingFrequency); |
| 162 | } |
| 163 | buffer[i] *= (amplitude / toneFrequencies.size()); |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | sp<AudioEffect> createEffect(const effect_uuid_t* type, |
| 168 | audio_session_t sessionId = AUDIO_SESSION_OUTPUT_MIX) { |
| 169 | std::string packageName{gPackageName}; |
| 170 | AttributionSourceState attributionSource; |
| 171 | attributionSource.packageName = packageName; |
| 172 | attributionSource.uid = VALUE_OR_FATAL(legacy2aidl_uid_t_int32_t(getuid())); |
| 173 | attributionSource.pid = VALUE_OR_FATAL(legacy2aidl_pid_t_int32_t(getpid())); |
| 174 | attributionSource.token = sp<BBinder>::make(); |
| 175 | sp<AudioEffect> effect = sp<AudioEffect>::make(attributionSource); |
| 176 | effect->set(type, nullptr, 0, nullptr, sessionId, AUDIO_IO_HANDLE_NONE, {}, false, false); |
| 177 | return effect; |
| 178 | } |
| 179 | |
| 180 | void computeFilterGainsAtTones(float captureDuration, int nPointFft, std::vector<int>& binOffsets, |
| 181 | float* inputMag, float* gaindB, const char* res, |
| 182 | audio_session_t sessionId) { |
| 183 | int totalFrameCount = captureDuration * kSamplingFrequency; |
| 184 | auto output = pffft::AlignedVector<float>(totalFrameCount); |
| 185 | auto fftOutput = pffft::AlignedVector<float>(nPointFft); |
| 186 | PlaybackEnv argsP; |
| 187 | argsP.mRes = std::string{res}; |
| 188 | argsP.mSessionId = sessionId; |
| 189 | CaptureEnv argsR; |
| 190 | argsR.mCaptureDuration = captureDuration; |
| 191 | std::thread playbackThread(&PlaybackEnv::play, &argsP); |
| 192 | std::thread captureThread(&CaptureEnv::capture, &argsR); |
| 193 | captureThread.join(); |
| 194 | playbackThread.join(); |
| 195 | ASSERT_EQ(OK, argsR.mStatus) << argsR.mMsg; |
| 196 | ASSERT_EQ(OK, argsP.mStatus) << argsP.mMsg; |
| 197 | ASSERT_FALSE(argsR.mDumpFileName.empty()) << "recorded not written to file"; |
| 198 | std::ifstream fin(argsR.mDumpFileName, std::ios::in | std::ios::binary); |
| 199 | fin.read((char*)output.data(), totalFrameCount * sizeof(output[0])); |
| 200 | fin.close(); |
| 201 | PFFFT_Setup* handle = pffft_new_setup(nPointFft, PFFFT_REAL); |
| 202 | // ignore first few samples. This is to not analyse until audio track is re-routed to remote |
| 203 | // submix source, also for the effect filter response to reach steady-state (priming / pruning |
| 204 | // samples). |
| 205 | int rerouteOffset = kPrimeDurationInSec * kSamplingFrequency; |
| 206 | pffft_transform_ordered(handle, output.data() + rerouteOffset, fftOutput.data(), nullptr, |
| 207 | PFFFT_FORWARD); |
| 208 | pffft_destroy_setup(handle); |
| 209 | for (auto i = 0; i < binOffsets.size(); i++) { |
| 210 | auto k = binOffsets[i]; |
| 211 | auto outputMag = sqrt((fftOutput[k * 2] * fftOutput[k * 2]) + |
| 212 | (fftOutput[k * 2 + 1] * fftOutput[k * 2 + 1])); |
| 213 | gaindB[i] = 20 * log10(outputMag / inputMag[i]); |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | std::tuple<int, int> roundToFreqCenteredToFftBin(float binWidth, float freq) { |
| 218 | int bin_index = std::round(freq / binWidth); |
| 219 | int cfreq = std::round(bin_index * binWidth); |
| 220 | return std::make_tuple(bin_index, cfreq); |
| 221 | } |
| 222 | |
| 223 | TEST(AudioEffectTest, CheckEqualizerEffect) { |
| 224 | audio_session_t sessionId = |
| 225 | (audio_session_t)AudioSystem::newAudioUniqueId(AUDIO_UNIQUE_ID_USE_SESSION); |
| 226 | sp<AudioEffect> equalizer = createEffect(SL_IID_EQUALIZER, sessionId); |
| 227 | ASSERT_EQ(OK, equalizer->initCheck()); |
| 228 | ASSERT_EQ(NO_ERROR, equalizer->setEnabled(true)); |
| 229 | if ((equalizer->descriptor().flags & EFFECT_FLAG_HW_ACC_MASK) != 0) { |
| 230 | GTEST_SKIP() << "effect processed output inaccessible, skipping test"; |
| 231 | } |
| 232 | #define MAX_PARAMS 64 |
| 233 | uint32_t buf32[sizeof(effect_param_t) / sizeof(uint32_t) + MAX_PARAMS]; |
| 234 | effect_param_t* eqParam = (effect_param_t*)(&buf32); |
| 235 | |
| 236 | // get num of presets |
| 237 | eqParam->psize = sizeof(uint32_t); |
| 238 | eqParam->vsize = sizeof(uint16_t); |
| 239 | *(int32_t*)eqParam->data = EQ_PARAM_GET_NUM_OF_PRESETS; |
| 240 | EXPECT_EQ(0, equalizer->getParameter(eqParam)); |
| 241 | EXPECT_EQ(0, eqParam->status); |
| 242 | int numPresets = *((uint16_t*)((int32_t*)eqParam->data + 1)); |
| 243 | |
| 244 | // get num of bands |
| 245 | eqParam->psize = sizeof(uint32_t); |
| 246 | eqParam->vsize = sizeof(uint16_t); |
| 247 | *(int32_t*)eqParam->data = EQ_PARAM_NUM_BANDS; |
| 248 | EXPECT_EQ(0, equalizer->getParameter(eqParam)); |
| 249 | EXPECT_EQ(0, eqParam->status); |
| 250 | int numBands = *((uint16_t*)((int32_t*)eqParam->data + 1)); |
| 251 | |
| 252 | const int totalFrameCount = kSamplingFrequency * kPlayBackDurationSec; |
| 253 | |
| 254 | // get band center frequencies |
| 255 | std::vector<int> centerFrequencies; |
| 256 | std::vector<int> binOffsets; |
| 257 | for (auto i = 0; i < numBands; i++) { |
| 258 | eqParam->psize = sizeof(uint32_t) * 2; |
| 259 | eqParam->vsize = sizeof(uint32_t); |
| 260 | *(int32_t*)eqParam->data = EQ_PARAM_CENTER_FREQ; |
| 261 | *((uint16_t*)((int32_t*)eqParam->data + 1)) = i; |
| 262 | EXPECT_EQ(0, equalizer->getParameter(eqParam)); |
| 263 | EXPECT_EQ(0, eqParam->status); |
| 264 | float cfreq = *((int32_t*)eqParam->data + 2) / 1000; // milli hz |
| 265 | // pick frequency close to bin center frequency |
| 266 | auto [bin_index, bin_freq] = roundToFreqCenteredToFftBin(kBinWidth, cfreq); |
| 267 | centerFrequencies.push_back(bin_freq); |
| 268 | binOffsets.push_back(bin_index); |
| 269 | } |
| 270 | |
| 271 | // input for effect module |
| 272 | auto input = pffft::AlignedVector<float>(totalFrameCount); |
| 273 | generateMultiTone(centerFrequencies, kSamplingFrequency, kPlayBackDurationSec, kDefAmplitude, |
| 274 | input.data(), totalFrameCount); |
| 275 | auto fftInput = pffft::AlignedVector<float>(kNPointFFT); |
| 276 | PFFFT_Setup* handle = pffft_new_setup(kNPointFFT, PFFFT_REAL); |
| 277 | pffft_transform_ordered(handle, input.data(), fftInput.data(), nullptr, PFFFT_FORWARD); |
| 278 | pffft_destroy_setup(handle); |
| 279 | float inputMag[numBands]; |
| 280 | for (auto i = 0; i < numBands; i++) { |
| 281 | auto k = binOffsets[i]; |
| 282 | inputMag[i] = sqrt((fftInput[k * 2] * fftInput[k * 2]) + |
| 283 | (fftInput[k * 2 + 1] * fftInput[k * 2 + 1])); |
| 284 | } |
| 285 | TemporaryFile tf("/data/local/tmp"); |
| 286 | close(tf.release()); |
| 287 | std::ofstream fout(tf.path, std::ios::out | std::ios::binary); |
| 288 | fout.write((char*)input.data(), input.size() * sizeof(input[0])); |
| 289 | fout.close(); |
| 290 | |
| 291 | float expGaindB[numBands], actGaindB[numBands]; |
| 292 | |
| 293 | std::string msg = ""; |
| 294 | int numPresetsOk = 0; |
| 295 | for (auto preset = 0; preset < numPresets; preset++) { |
| 296 | // set preset |
| 297 | eqParam->psize = sizeof(uint32_t); |
| 298 | eqParam->vsize = sizeof(uint32_t); |
| 299 | *(int32_t*)eqParam->data = EQ_PARAM_CUR_PRESET; |
| 300 | *((uint16_t*)((int32_t*)eqParam->data + 1)) = preset; |
| 301 | EXPECT_EQ(0, equalizer->setParameter(eqParam)); |
| 302 | EXPECT_EQ(0, eqParam->status); |
| 303 | // get preset gains |
| 304 | eqParam->psize = sizeof(uint32_t); |
| 305 | eqParam->vsize = (numBands + 1) * sizeof(uint32_t); |
| 306 | *(int32_t*)eqParam->data = EQ_PARAM_PROPERTIES; |
| 307 | EXPECT_EQ(0, equalizer->getParameter(eqParam)); |
| 308 | EXPECT_EQ(0, eqParam->status); |
| 309 | t_equalizer_settings* settings = |
| 310 | reinterpret_cast<t_equalizer_settings*>((int32_t*)eqParam->data + 1); |
| 311 | EXPECT_EQ(preset, settings->curPreset); |
| 312 | EXPECT_EQ(numBands, settings->numBands); |
| 313 | for (auto i = 0; i < numBands; i++) { |
| 314 | expGaindB[i] = ((int16_t)settings->bandLevels[i]) / 100.0f; // gain in milli bels |
| 315 | } |
| 316 | memset(actGaindB, 0, sizeof(actGaindB)); |
| 317 | ASSERT_NO_FATAL_FAILURE(computeFilterGainsAtTones(kCaptureDurationSec, kNPointFFT, |
| 318 | binOffsets, inputMag, actGaindB, tf.path, |
| 319 | sessionId)); |
| 320 | bool isOk = true; |
| 321 | for (auto i = 0; i < numBands - 1; i++) { |
| 322 | auto diffA = expGaindB[i] - expGaindB[i + 1]; |
| 323 | auto diffB = actGaindB[i] - actGaindB[i + 1]; |
| 324 | if (diffA == 0 && fabs(diffA - diffB) > 1.0f) { |
| 325 | msg += (android::base::StringPrintf( |
| 326 | "For eq preset : %d, between bands %d and %d, expected relative gain is : " |
| 327 | "%f, got relative gain is : %f, error : %f \n", |
| 328 | preset, i, i + 1, diffA, diffB, diffA - diffB)); |
| 329 | isOk = false; |
| 330 | } else if (diffA * diffB < 0) { |
| 331 | msg += (android::base::StringPrintf( |
| 332 | "For eq preset : %d, between bands %d and %d, expected relative gain and " |
| 333 | "seen relative gain are of opposite signs \n. Expected relative gain is : " |
| 334 | "%f, seen relative gain is : %f \n", |
| 335 | preset, i, i + 1, diffA, diffB)); |
| 336 | isOk = false; |
| 337 | } |
| 338 | } |
| 339 | if (isOk) numPresetsOk++; |
| 340 | } |
| 341 | EXPECT_EQ(numPresetsOk, numPresets) << msg; |
| 342 | } |
| 343 | |
| 344 | TEST(AudioEffectTest, CheckBassBoostEffect) { |
| 345 | audio_session_t sessionId = |
| 346 | (audio_session_t)AudioSystem::newAudioUniqueId(AUDIO_UNIQUE_ID_USE_SESSION); |
| 347 | sp<AudioEffect> bassboost = createEffect(SL_IID_BASSBOOST, sessionId); |
| 348 | ASSERT_EQ(OK, bassboost->initCheck()); |
| 349 | ASSERT_EQ(NO_ERROR, bassboost->setEnabled(true)); |
| 350 | if ((bassboost->descriptor().flags & EFFECT_FLAG_HW_ACC_MASK) != 0) { |
| 351 | GTEST_SKIP() << "effect processed output inaccessible, skipping test"; |
| 352 | } |
| 353 | int32_t buf32[sizeof(effect_param_t) / sizeof(int32_t) + MAX_PARAMS]; |
| 354 | effect_param_t* bbParam = (effect_param_t*)(&buf32); |
| 355 | |
| 356 | bbParam->psize = sizeof(int32_t); |
| 357 | bbParam->vsize = sizeof(int32_t); |
| 358 | *(int32_t*)bbParam->data = BASSBOOST_PARAM_STRENGTH_SUPPORTED; |
| 359 | EXPECT_EQ(0, bassboost->getParameter(bbParam)); |
| 360 | EXPECT_EQ(0, bbParam->status); |
| 361 | bool strengthSupported = *((int32_t*)bbParam->data + 1); |
| 362 | |
| 363 | const int totalFrameCount = kSamplingFrequency * kPlayBackDurationSec; |
| 364 | |
| 365 | // selecting bass frequency, speech tone (for relative gain) |
| 366 | std::vector<int> testFrequencies{100, 1200}; |
| 367 | std::vector<int> binOffsets; |
| 368 | for (auto i = 0; i < testFrequencies.size(); i++) { |
| 369 | // pick frequency close to bin center frequency |
| 370 | auto [bin_index, bin_freq] = roundToFreqCenteredToFftBin(kBinWidth, testFrequencies[i]); |
| 371 | testFrequencies[i] = bin_freq; |
| 372 | binOffsets.push_back(bin_index); |
| 373 | } |
| 374 | |
| 375 | // input for effect module |
| 376 | auto input = pffft::AlignedVector<float>(totalFrameCount); |
| 377 | generateMultiTone(testFrequencies, kSamplingFrequency, kPlayBackDurationSec, kDefAmplitude, |
| 378 | input.data(), totalFrameCount); |
| 379 | auto fftInput = pffft::AlignedVector<float>(kNPointFFT); |
| 380 | PFFFT_Setup* handle = pffft_new_setup(kNPointFFT, PFFFT_REAL); |
| 381 | pffft_transform_ordered(handle, input.data(), fftInput.data(), nullptr, PFFFT_FORWARD); |
| 382 | pffft_destroy_setup(handle); |
| 383 | float inputMag[testFrequencies.size()]; |
| 384 | for (auto i = 0; i < testFrequencies.size(); i++) { |
| 385 | auto k = binOffsets[i]; |
| 386 | inputMag[i] = sqrt((fftInput[k * 2] * fftInput[k * 2]) + |
| 387 | (fftInput[k * 2 + 1] * fftInput[k * 2 + 1])); |
| 388 | } |
| 389 | TemporaryFile tf("/data/local/tmp"); |
| 390 | close(tf.release()); |
| 391 | std::ofstream fout(tf.path, std::ios::out | std::ios::binary); |
| 392 | fout.write((char*)input.data(), input.size() * sizeof(input[0])); |
| 393 | fout.close(); |
| 394 | |
| 395 | float gainWithOutFilter[testFrequencies.size()]; |
| 396 | memset(gainWithOutFilter, 0, sizeof(gainWithOutFilter)); |
| 397 | ASSERT_NO_FATAL_FAILURE(computeFilterGainsAtTones(kCaptureDurationSec, kNPointFFT, binOffsets, |
| 398 | inputMag, gainWithOutFilter, tf.path, |
| 399 | AUDIO_SESSION_OUTPUT_MIX)); |
| 400 | float diffA = gainWithOutFilter[0] - gainWithOutFilter[1]; |
| 401 | float prevGain = -100.f; |
| 402 | for (auto strength = 150; strength < 1000; strength += strengthSupported ? 150 : 1000) { |
| 403 | // configure filter strength |
| 404 | if (strengthSupported) { |
| 405 | bbParam->psize = sizeof(int32_t); |
| 406 | bbParam->vsize = sizeof(int16_t); |
| 407 | *(int32_t*)bbParam->data = BASSBOOST_PARAM_STRENGTH; |
| 408 | *((int16_t*)((int32_t*)bbParam->data + 1)) = strength; |
| 409 | EXPECT_EQ(0, bassboost->setParameter(bbParam)); |
| 410 | EXPECT_EQ(0, bbParam->status); |
| 411 | } |
| 412 | float gainWithFilter[testFrequencies.size()]; |
| 413 | memset(gainWithFilter, 0, sizeof(gainWithFilter)); |
| 414 | ASSERT_NO_FATAL_FAILURE(computeFilterGainsAtTones(kCaptureDurationSec, kNPointFFT, |
| 415 | binOffsets, inputMag, gainWithFilter, |
| 416 | tf.path, sessionId)); |
| 417 | float diffB = gainWithFilter[0] - gainWithFilter[1]; |
| 418 | EXPECT_GT(diffB, diffA) << "bassboost effect not seen"; |
| 419 | EXPECT_GE(diffB, prevGain) << "increase in boost strength causing fall in gain"; |
| 420 | prevGain = diffB; |
| 421 | } |
| 422 | } |
Mikhail Naganov | f84a558 | 2024-02-15 11:26:33 -0800 | [diff] [blame] | 423 | |
| 424 | int main(int argc, char** argv) { |
| 425 | android::ProcessState::self()->startThreadPool(); |
| 426 | ::testing::InitGoogleTest(&argc, argv); |
| 427 | ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer()); |
| 428 | return RUN_ALL_TESTS(); |
| 429 | } |