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 
17 #include <fstream>
18 #include <iostream>
19 #include <string>
20 #include <tuple>
21 #include <vector>
22 
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 
34 #include "audio_test_utils.h"
35 #include "pffft.hpp"
36 #include "test_execution_tracer.h"
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 
~CaptureEnv()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 
capture()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 
play()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 
generateMultiTone(const std::vector<int> & toneFrequencies,float samplingFrequency,float duration,float amplitude,float * buffer,int numSamples)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 
createEffect(const effect_uuid_t * type,audio_session_t sessionId=AUDIO_SESSION_OUTPUT_MIX)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 
computeFilterGainsAtTones(float captureDuration,int nPointFft,std::vector<int> & binOffsets,float * inputMag,float * gaindB,const char * res,audio_session_t sessionId)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 
roundToFreqCenteredToFftBin(float binWidth,float freq)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 
TEST(AudioEffectTest,CheckEqualizerEffect)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 
TEST(AudioEffectTest,CheckBassBoostEffect)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 }
423 
main(int argc,char ** argv)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 }
430