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