blob: aae09ded14e0d867cfb6f6868a37ec5ccab1eb2c [file] [log] [blame]
Harish Mahendrakar9f33dba2021-02-17 18:25:21 -08001/*
2 * Copyright 2021 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 <array>
18#include <audio_utils/channels.h>
19#include <audio_utils/primitives.h>
20#include <climits>
21#include <cstdlib>
22#include <gtest/gtest.h>
23#include <hardware/audio_effect.h>
24#include <log/log.h>
25#include <random>
26#include <system/audio.h>
27#include <vector>
28
29extern audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM;
30
31// Corresponds to SNR for 1 bit difference between two int16_t signals
32constexpr float kSNRThreshold = 90.308998;
33
34// Update isBassBoost, if the order of effects is updated
35constexpr effect_uuid_t kEffectUuids[] = {
36 // NXP SW BassBoost
37 {0x8631f300, 0x72e2, 0x11df, 0xb57e, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}},
38 // NXP SW Virtualizer
39 {0x1d4033c0, 0x8557, 0x11df, 0x9f2d, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}},
40 // NXP SW Equalizer
41 {0xce772f20, 0x847d, 0x11df, 0xbb17, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}},
42 // NXP SW Volume
43 {0x119341a0, 0x8469, 0x11df, 0x81f9, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}},
44};
45
46static bool isBassBoost(const effect_uuid_t* uuid) {
47 // Update this, if the order of effects in kEffectUuids is updated
48 return uuid == &kEffectUuids[0];
49}
50
51constexpr size_t kNumEffectUuids = std::size(kEffectUuids);
52
53constexpr audio_channel_mask_t kChMasks[] = {
54 AUDIO_CHANNEL_OUT_MONO, AUDIO_CHANNEL_OUT_STEREO,
55 AUDIO_CHANNEL_OUT_2POINT1, AUDIO_CHANNEL_OUT_2POINT0POINT2,
56 AUDIO_CHANNEL_OUT_QUAD, AUDIO_CHANNEL_OUT_QUAD_BACK,
57 AUDIO_CHANNEL_OUT_QUAD_SIDE, AUDIO_CHANNEL_OUT_SURROUND,
58 AUDIO_CHANNEL_INDEX_MASK_4, AUDIO_CHANNEL_OUT_2POINT1POINT2,
59 AUDIO_CHANNEL_OUT_3POINT0POINT2, AUDIO_CHANNEL_OUT_PENTA,
60 AUDIO_CHANNEL_INDEX_MASK_5, AUDIO_CHANNEL_OUT_3POINT1POINT2,
61 AUDIO_CHANNEL_OUT_5POINT1, AUDIO_CHANNEL_OUT_5POINT1_BACK,
62 AUDIO_CHANNEL_OUT_5POINT1_SIDE, AUDIO_CHANNEL_INDEX_MASK_6,
63 AUDIO_CHANNEL_OUT_6POINT1, AUDIO_CHANNEL_INDEX_MASK_7,
64 AUDIO_CHANNEL_OUT_5POINT1POINT2, AUDIO_CHANNEL_OUT_7POINT1,
65 AUDIO_CHANNEL_INDEX_MASK_8, AUDIO_CHANNEL_INDEX_MASK_9,
66 AUDIO_CHANNEL_INDEX_MASK_10, AUDIO_CHANNEL_INDEX_MASK_11,
67 AUDIO_CHANNEL_INDEX_MASK_12, AUDIO_CHANNEL_INDEX_MASK_13,
68 AUDIO_CHANNEL_INDEX_MASK_14, AUDIO_CHANNEL_INDEX_MASK_15,
69 AUDIO_CHANNEL_INDEX_MASK_16, AUDIO_CHANNEL_INDEX_MASK_17,
70 AUDIO_CHANNEL_INDEX_MASK_18, AUDIO_CHANNEL_INDEX_MASK_19,
71 AUDIO_CHANNEL_INDEX_MASK_20, AUDIO_CHANNEL_INDEX_MASK_21,
72 AUDIO_CHANNEL_INDEX_MASK_22, AUDIO_CHANNEL_INDEX_MASK_23,
73 AUDIO_CHANNEL_INDEX_MASK_24,
74};
75
76constexpr size_t kNumChMasks = std::size(kChMasks);
77
78constexpr size_t kSampleRates[] = {8000, 11025, 12000, 16000, 22050, 24000, 32000,
79 44100, 48000, 88200, 96000, 176400, 192000};
80
81constexpr size_t kNumSampleRates = std::size(kSampleRates);
82
83constexpr size_t kFrameCounts[] = {4, 2048};
84
85constexpr size_t kNumFrameCounts = std::size(kFrameCounts);
86
87constexpr size_t kLoopCounts[] = {1, 4};
88
89constexpr size_t kNumLoopCounts = std::size(kLoopCounts);
90
91class EffectBundleHelper {
92 public:
93 EffectBundleHelper(const effect_uuid_t* uuid, size_t chMask, size_t sampleRate,
94 size_t frameCount, size_t loopCount)
95 : mUuid(uuid),
96 mChMask(chMask),
97 mChannelCount(audio_channel_count_from_out_mask(mChMask)),
98 mSampleRate(sampleRate),
99 mFrameCount(frameCount),
100 mLoopCount(loopCount) {}
101 void createEffect();
102 void releaseEffect();
103 void configEffect();
104 void process(float* input, float* output);
105
106 private:
107 const effect_uuid_t* mUuid;
108 const size_t mChMask;
109 const size_t mChannelCount;
110 const size_t mSampleRate;
111 const size_t mFrameCount;
112 const size_t mLoopCount;
113 effect_handle_t mEffectHandle{};
114};
115
116void EffectBundleHelper::createEffect() {
117 int status = AUDIO_EFFECT_LIBRARY_INFO_SYM.create_effect(mUuid, 1, 1, &mEffectHandle);
118 ASSERT_EQ(status, 0) << "create_effect returned an error " << status << "\n";
119}
120
121void EffectBundleHelper::releaseEffect() {
122 int status = AUDIO_EFFECT_LIBRARY_INFO_SYM.release_effect(mEffectHandle);
123 ASSERT_EQ(status, 0) << "release_effect returned an error " << status << "\n";
124}
125
126void EffectBundleHelper::configEffect() {
127 effect_config_t config{};
128 config.inputCfg.samplingRate = config.outputCfg.samplingRate = mSampleRate;
129 config.inputCfg.channels = config.outputCfg.channels = mChMask;
130 config.inputCfg.format = config.outputCfg.format = AUDIO_FORMAT_PCM_FLOAT;
131
132 int reply = 0;
133 uint32_t replySize = sizeof(reply);
134 int status = (*mEffectHandle)
135 ->command(mEffectHandle, EFFECT_CMD_SET_CONFIG, sizeof(effect_config_t),
136 &config, &replySize, &reply);
137 ASSERT_EQ(status, 0) << "command returned an error " << status << "\n";
138 ASSERT_EQ(reply, 0) << "command reply non zero " << reply << "\n";
139
140 status = (*mEffectHandle)
141 ->command(mEffectHandle, EFFECT_CMD_ENABLE, 0, nullptr, &replySize, &reply);
142 ASSERT_EQ(status, 0) << "command enable returned an error " << status << "\n";
143 ASSERT_EQ(reply, 0) << "command reply non zero " << reply << "\n";
144}
145
146void EffectBundleHelper::process(float* input, float* output) {
147 audio_buffer_t inBuffer = {.frameCount = mFrameCount, .f32 = input};
148 audio_buffer_t outBuffer = {.frameCount = mFrameCount, .f32 = output};
149 for (size_t i = 0; i < mLoopCount; i++) {
150 int status = (*mEffectHandle)->process(mEffectHandle, &inBuffer, &outBuffer);
151 ASSERT_EQ(status, 0) << "process returned an error " << status << "\n";
152
153 inBuffer.f32 += mFrameCount * mChannelCount;
154 outBuffer.f32 += mFrameCount * mChannelCount;
155 }
156}
157
158typedef std::tuple<int, int, int, int, int> SingleEffectTestParam;
159class SingleEffectTest : public ::testing::TestWithParam<SingleEffectTestParam> {
160 public:
161 SingleEffectTest()
162 : mChMask(kChMasks[std::get<0>(GetParam())]),
163 mChannelCount(audio_channel_count_from_out_mask(mChMask)),
164 mSampleRate(kSampleRates[std::get<1>(GetParam())]),
165 mFrameCount(kFrameCounts[std::get<2>(GetParam())]),
166 mLoopCount(kLoopCounts[std::get<3>(GetParam())]),
167 mTotalFrameCount(mFrameCount * mLoopCount),
168 mUuid(&kEffectUuids[std::get<4>(GetParam())]) {}
169
170 const size_t mChMask;
171 const size_t mChannelCount;
172 const size_t mSampleRate;
173 const size_t mFrameCount;
174 const size_t mLoopCount;
175 const size_t mTotalFrameCount;
176 const effect_uuid_t* mUuid;
177};
178
179// Tests applying a single effect
180TEST_P(SingleEffectTest, SimpleProcess) {
181 SCOPED_TRACE(testing::Message()
182 << "chMask: " << mChMask << " sampleRate: " << mSampleRate
183 << " frameCount: " << mFrameCount << " loopCount: " << mLoopCount);
184
185 EffectBundleHelper effect(mUuid, mChMask, mSampleRate, mFrameCount, mLoopCount);
186
187 ASSERT_NO_FATAL_FAILURE(effect.createEffect());
188 ASSERT_NO_FATAL_FAILURE(effect.configEffect());
189
190 // Initialize input buffer with deterministic pseudo-random values
191 std::vector<float> input(mTotalFrameCount * mChannelCount);
192 std::vector<float> output(mTotalFrameCount * mChannelCount);
193 std::minstd_rand gen(mChMask);
194 std::uniform_real_distribution<> dis(-1.0f, 1.0f);
195 for (auto& in : input) {
196 in = dis(gen);
197 }
198 ASSERT_NO_FATAL_FAILURE(effect.process(input.data(), output.data()));
199 ASSERT_NO_FATAL_FAILURE(effect.releaseEffect());
200}
201
202INSTANTIATE_TEST_SUITE_P(EffectBundleTestAll, SingleEffectTest,
203 ::testing::Combine(::testing::Range(0, (int)kNumChMasks),
204 ::testing::Range(0, (int)kNumSampleRates),
205 ::testing::Range(0, (int)kNumFrameCounts),
206 ::testing::Range(0, (int)kNumLoopCounts),
207 ::testing::Range(0, (int)kNumEffectUuids)));
208
209typedef std::tuple<int, int, int, int> SingleEffectComparisonTestParam;
210class SingleEffectComparisonTest
211 : public ::testing::TestWithParam<SingleEffectComparisonTestParam> {
212 public:
213 SingleEffectComparisonTest()
214 : mSampleRate(kSampleRates[std::get<0>(GetParam())]),
215 mFrameCount(kFrameCounts[std::get<1>(GetParam())]),
216 mLoopCount(kLoopCounts[std::get<2>(GetParam())]),
217 mTotalFrameCount(mFrameCount * mLoopCount),
218 mUuid(&kEffectUuids[std::get<3>(GetParam())]) {}
219
220 const size_t mSampleRate;
221 const size_t mFrameCount;
222 const size_t mLoopCount;
223 const size_t mTotalFrameCount;
224 const effect_uuid_t* mUuid;
225};
226
227template <typename T>
228float computeSnr(const T* ref, const T* tst, size_t count) {
229 double signal{};
230 double noise{};
231
232 for (size_t i = 0; i < count; ++i) {
233 const double value(ref[i]);
234 const double diff(tst[i] - value);
235 signal += value * value;
236 noise += diff * diff;
237 }
238 // Initialized to a value greater than kSNRThreshold to handle
239 // cases where ref and tst match exactly
240 float snr = kSNRThreshold + 1.0f;
241 if (signal > 0.0f && noise > 0.0f) {
242 snr = 10.f * log(signal / noise);
243 }
244 return snr;
245}
246
247// Compares first two channels in multi-channel output to stereo output when same effect is applied
248TEST_P(SingleEffectComparisonTest, SimpleProcess) {
249 SCOPED_TRACE(testing::Message() << " sampleRate: " << mSampleRate << " frameCount: "
250 << mFrameCount << " loopCount: " << mLoopCount);
251
252 // Initialize mono input buffer with deterministic pseudo-random values
253 std::vector<float> monoInput(mTotalFrameCount);
254
255 std::minstd_rand gen(mSampleRate);
256 std::uniform_real_distribution<> dis(-1.0f, 1.0f);
257 for (auto& in : monoInput) {
258 in = dis(gen);
259 }
260
261 // Generate stereo by repeating mono channel data
262 std::vector<float> stereoInput(mTotalFrameCount * FCC_2);
263 adjust_channels(monoInput.data(), FCC_1, stereoInput.data(), FCC_2, sizeof(float),
264 mTotalFrameCount * sizeof(float) * FCC_1);
265
266 // Apply effect on stereo channels
267 EffectBundleHelper stereoEffect(mUuid, AUDIO_CHANNEL_OUT_STEREO, mSampleRate, mFrameCount,
268 mLoopCount);
269
270 ASSERT_NO_FATAL_FAILURE(stereoEffect.createEffect());
271 ASSERT_NO_FATAL_FAILURE(stereoEffect.configEffect());
272
273 std::vector<float> stereoOutput(mTotalFrameCount * FCC_2);
274 ASSERT_NO_FATAL_FAILURE(stereoEffect.process(stereoInput.data(), stereoOutput.data()));
275 ASSERT_NO_FATAL_FAILURE(stereoEffect.releaseEffect());
276
277 // Convert stereo float data to stereo int16_t to be used as reference
278 std::vector<int16_t> stereoRefI16(mTotalFrameCount * FCC_2);
279 memcpy_to_i16_from_float(stereoRefI16.data(), stereoOutput.data(), mTotalFrameCount * FCC_2);
280
281 for (size_t chMask : kChMasks) {
282 size_t channelCount = audio_channel_count_from_out_mask(chMask);
283 EffectBundleHelper testEffect(mUuid, chMask, mSampleRate, mFrameCount, mLoopCount);
284
285 ASSERT_NO_FATAL_FAILURE(testEffect.createEffect());
286 ASSERT_NO_FATAL_FAILURE(testEffect.configEffect());
287
288 std::vector<float> testInput(mTotalFrameCount * channelCount);
289
290 // Repeat mono channel data to all the channels
291 // adjust_channels() zero fills channels > 2, hence can't be used here
292 for (size_t i = 0; i < mTotalFrameCount; ++i) {
293 auto* fp = &testInput[i * channelCount];
294 std::fill(fp, fp + channelCount, monoInput[i]);
295 }
296
297 std::vector<float> testOutput(mTotalFrameCount * channelCount);
298 ASSERT_NO_FATAL_FAILURE(testEffect.process(testInput.data(), testOutput.data()));
299 ASSERT_NO_FATAL_FAILURE(testEffect.releaseEffect());
300
301 // Extract first two channels
302 std::vector<float> stereoTestOutput(mTotalFrameCount * FCC_2);
303 adjust_channels(testOutput.data(), channelCount, stereoTestOutput.data(), FCC_2,
304 sizeof(float), mTotalFrameCount * sizeof(float) * channelCount);
305
306 // Convert the test data to int16_t
307 std::vector<int16_t> stereoTestI16(mTotalFrameCount * FCC_2);
308 memcpy_to_i16_from_float(stereoTestI16.data(), stereoTestOutput.data(),
309 mTotalFrameCount * FCC_2);
310
311 if (isBassBoost(mUuid)) {
312 // SNR must be above the threshold
313 float snr = computeSnr<int16_t>(stereoRefI16.data(), stereoTestI16.data(),
314 mTotalFrameCount * FCC_2);
315 ASSERT_GT(snr, kSNRThreshold) << "SNR " << snr << "is lower than " << kSNRThreshold;
316 } else {
317 ASSERT_EQ(0,
318 memcmp(stereoRefI16.data(), stereoTestI16.data(), mTotalFrameCount * FCC_2))
319 << "First two channels do not match with stereo output \n";
320 }
321 }
322}
323
324INSTANTIATE_TEST_SUITE_P(EffectBundleTestAll, SingleEffectComparisonTest,
325 ::testing::Combine(::testing::Range(0, (int)kNumSampleRates),
326 ::testing::Range(0, (int)kNumFrameCounts),
327 ::testing::Range(0, (int)kNumLoopCounts),
328 ::testing::Range(0, (int)kNumEffectUuids)));
329
330int main(int argc, char** argv) {
331 ::testing::InitGoogleTest(&argc, argv);
332 int status = RUN_ALL_TESTS();
333 ALOGV("Test result = %d\n", status);
334 return status;
335}