blob: 6f75f5aabf1617d5117268ed65f3a975b0572c6f [file] [log] [blame]
Phil Burkbb78a732018-03-28 15:37:19 -07001/*
2 * Copyright 2018 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/*
18 * Test FlowGraph
Phil Burke2ac8fa2022-05-19 22:27:23 +000019 *
20 * This file also tests a few different conversion techniques because
21 * sometimes that have caused compiler bugs.
Phil Burkbb78a732018-03-28 15:37:19 -070022 */
23
24#include <iostream>
25
26#include <gtest/gtest.h>
27
28#include "flowgraph/ClipToRange.h"
Robert Wu67375a12022-08-25 22:04:29 +000029#include "flowgraph/Limiter.h"
Robert Wud7400832021-12-04 01:11:19 +000030#include "flowgraph/MonoBlend.h"
Phil Burkbb78a732018-03-28 15:37:19 -070031#include "flowgraph/MonoToMultiConverter.h"
32#include "flowgraph/SourceFloat.h"
33#include "flowgraph/RampLinear.h"
34#include "flowgraph/SinkFloat.h"
35#include "flowgraph/SinkI16.h"
36#include "flowgraph/SinkI24.h"
Phil Burke2ac8fa2022-05-19 22:27:23 +000037#include "flowgraph/SinkI32.h"
Phil Burkbb78a732018-03-28 15:37:19 -070038#include "flowgraph/SourceI16.h"
39#include "flowgraph/SourceI24.h"
40
Robert Wuedc850a2022-04-05 16:47:03 +000041using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
Phil Burkbb78a732018-03-28 15:37:19 -070042
43constexpr int kBytesPerI24Packed = 3;
44
Phil Burke2ac8fa2022-05-19 22:27:23 +000045constexpr int kNumSamples = 8;
46constexpr std::array<float, kNumSamples> kInputFloat = {
47 1.0f, 0.5f, -0.25f, -1.0f,
48 0.0f, 53.9f, -87.2f, -1.02f};
49
50// Corresponding PCM values as integers.
51constexpr std::array<int16_t, kNumSamples> kExpectedI16 = {
52 INT16_MAX, 1 << 14, INT16_MIN / 4, INT16_MIN,
53 0, INT16_MAX, INT16_MIN, INT16_MIN};
54
55constexpr std::array<int32_t, kNumSamples> kExpectedI32 = {
56 INT32_MAX, 1 << 30, INT32_MIN / 4, INT32_MIN,
57 0, INT32_MAX, INT32_MIN, INT32_MIN};
58
59// =================================== FLOAT to I16 ==============
60
Phil Burk30e58932022-03-29 00:12:44 +000061// Simple test that tries to reproduce a Clang compiler bug.
62__attribute__((noinline))
63void local_convert_float_to_int16(const float *input,
64 int16_t *output,
65 int count) {
66 for (int i = 0; i < count; i++) {
67 int32_t n = (int32_t) (*input++ * 32768.0f);
68 *output++ = std::min(INT16_MAX, std::max(INT16_MIN, n)); // clip
69 }
70}
71
72TEST(test_flowgraph, local_convert_float_int16) {
Phil Burk30e58932022-03-29 00:12:44 +000073 std::array<int16_t, kNumSamples> output;
74
75 // Do it inline, which will probably work even with the buggy compiler.
76 // This validates the expected data.
Phil Burke2ac8fa2022-05-19 22:27:23 +000077 const float *in = kInputFloat.data();
Phil Burk30e58932022-03-29 00:12:44 +000078 int16_t *out = output.data();
79 output.fill(777);
80 for (int i = 0; i < kNumSamples; i++) {
81 int32_t n = (int32_t) (*in++ * 32768.0f);
82 *out++ = std::min(INT16_MAX, std::max(INT16_MIN, n)); // clip
83 }
84 for (int i = 0; i < kNumSamples; i++) {
Phil Burke2ac8fa2022-05-19 22:27:23 +000085 EXPECT_EQ(kExpectedI16.at(i), output.at(i)) << ", i = " << i;
Phil Burk30e58932022-03-29 00:12:44 +000086 }
87
88 // Convert audio signal using the function.
89 output.fill(777);
Phil Burke2ac8fa2022-05-19 22:27:23 +000090 local_convert_float_to_int16(kInputFloat.data(), output.data(), kNumSamples);
Phil Burk30e58932022-03-29 00:12:44 +000091 for (int i = 0; i < kNumSamples; i++) {
Phil Burke2ac8fa2022-05-19 22:27:23 +000092 EXPECT_EQ(kExpectedI16.at(i), output.at(i)) << ", i = " << i;
Phil Burk30e58932022-03-29 00:12:44 +000093 }
94}
95
Phil Burkbb78a732018-03-28 15:37:19 -070096TEST(test_flowgraph, module_sinki16) {
Phil Burk30e58932022-03-29 00:12:44 +000097 static constexpr int kNumSamples = 8;
Phil Burk30e58932022-03-29 00:12:44 +000098 std::array<int16_t, kNumSamples + 10> output; // larger than input
99
Phil Burkbb78a732018-03-28 15:37:19 -0700100 SourceFloat sourceFloat{1};
101 SinkI16 sinkI16{1};
102
Phil Burke2ac8fa2022-05-19 22:27:23 +0000103 sourceFloat.setData(kInputFloat.data(), kNumSamples);
Phil Burkbb78a732018-03-28 15:37:19 -0700104 sourceFloat.output.connect(&sinkI16.input);
105
Phil Burk30e58932022-03-29 00:12:44 +0000106 output.fill(777);
107 int32_t numRead = sinkI16.read(output.data(), output.size());
108 ASSERT_EQ(kNumSamples, numRead);
Phil Burkbb78a732018-03-28 15:37:19 -0700109 for (int i = 0; i < numRead; i++) {
Phil Burke2ac8fa2022-05-19 22:27:23 +0000110 EXPECT_EQ(kExpectedI16.at(i), output.at(i)) << ", i = " << i;
111 }
112}
113
114// =================================== FLOAT to I32 ==============
115// Simple test that tries to reproduce a Clang compiler bug.
116__attribute__((noinline))
117static int32_t clamp32FromFloat(float f)
118{
119 static const float scale = (float)(1UL << 31);
120 static const float limpos = 1.;
121 static const float limneg = -1.;
122
123 if (f <= limneg) {
124 return INT32_MIN;
125 } else if (f >= limpos) {
126 return INT32_MAX;
127 }
128 f *= scale;
129 /* integer conversion is through truncation (though int to float is not).
130 * ensure that we round to nearest, ties away from 0.
131 */
132 return f > 0 ? f + 0.5 : f - 0.5;
133}
134
135void local_convert_float_to_int32(const float *input,
136 int32_t *output,
137 int count) {
138 for (int i = 0; i < count; i++) {
139 *output++ = clamp32FromFloat(*input++);
140 }
141}
142
143TEST(test_flowgraph, simple_convert_float_int32) {
144 std::array<int32_t, kNumSamples> output;
145
146 // Do it inline, which will probably work even with a buggy compiler.
147 // This validates the expected data.
148 const float *in = kInputFloat.data();
149 output.fill(777);
150 int32_t *out = output.data();
151 for (int i = 0; i < kNumSamples; i++) {
152 int64_t n = (int64_t) (*in++ * 2147483648.0f);
153 *out++ = (int32_t)std::min((int64_t)INT32_MAX,
154 std::max((int64_t)INT32_MIN, n)); // clip
155 }
156 for (int i = 0; i < kNumSamples; i++) {
157 EXPECT_EQ(kExpectedI32.at(i), output.at(i)) << ", i = " << i;
158 }
159}
160
161TEST(test_flowgraph, local_convert_float_int32) {
162 std::array<int32_t, kNumSamples> output;
163 // Convert audio signal using the function.
164 output.fill(777);
165 local_convert_float_to_int32(kInputFloat.data(), output.data(), kNumSamples);
166 for (int i = 0; i < kNumSamples; i++) {
167 EXPECT_EQ(kExpectedI32.at(i), output.at(i)) << ", i = " << i;
168 }
169}
170
171TEST(test_flowgraph, module_sinki32) {
172 std::array<int32_t, kNumSamples + 10> output; // larger than input
173
174 SourceFloat sourceFloat{1};
175 SinkI32 sinkI32{1};
176
177 sourceFloat.setData(kInputFloat.data(), kNumSamples);
178 sourceFloat.output.connect(&sinkI32.input);
179
180 output.fill(777);
181 int32_t numRead = sinkI32.read(output.data(), output.size());
182 ASSERT_EQ(kNumSamples, numRead);
183 for (int i = 0; i < numRead; i++) {
184 EXPECT_EQ(kExpectedI32.at(i), output.at(i)) << ", i = " << i;
Phil Burkbb78a732018-03-28 15:37:19 -0700185 }
186}
187
188TEST(test_flowgraph, module_mono_to_stereo) {
189 static const float input[] = {1.0f, 2.0f, 3.0f};
190 float output[100] = {};
191 SourceFloat sourceFloat{1};
192 MonoToMultiConverter monoToStereo{2};
193 SinkFloat sinkFloat{2};
194
195 sourceFloat.setData(input, 3);
196
197 sourceFloat.output.connect(&monoToStereo.input);
198 monoToStereo.output.connect(&sinkFloat.input);
199
200 int32_t numRead = sinkFloat.read(output, 8);
201 ASSERT_EQ(3, numRead);
202 EXPECT_EQ(input[0], output[0]);
203 EXPECT_EQ(input[0], output[1]);
204 EXPECT_EQ(input[1], output[2]);
205 EXPECT_EQ(input[1], output[3]);
206}
207
208TEST(test_flowgraph, module_ramp_linear) {
Robert Wuc3278fe2021-06-23 16:51:40 +0000209 constexpr int singleNumOutput = 1;
Phil Burkbb78a732018-03-28 15:37:19 -0700210 constexpr int rampSize = 5;
211 constexpr int numOutput = 100;
212 constexpr float value = 1.0f;
Robert Wuc3278fe2021-06-23 16:51:40 +0000213 constexpr float initialTarget = 10.0f;
214 constexpr float finalTarget = 100.0f;
215 constexpr float tolerance = 0.0001f; // arbitrary
Phil Burkbb78a732018-03-28 15:37:19 -0700216 float output[numOutput] = {};
217 RampLinear rampLinear{1};
218 SinkFloat sinkFloat{1};
219
220 rampLinear.input.setValue(value);
221 rampLinear.setLengthInFrames(rampSize);
Phil Burkbb78a732018-03-28 15:37:19 -0700222 rampLinear.output.connect(&sinkFloat.input);
223
Robert Wuc3278fe2021-06-23 16:51:40 +0000224 // Check that the values go to the initial target instantly.
225 rampLinear.setTarget(initialTarget);
226 int32_t singleNumRead = sinkFloat.read(output, singleNumOutput);
227 ASSERT_EQ(singleNumRead, singleNumOutput);
228 EXPECT_NEAR(value * initialTarget, output[0], tolerance);
229
230 // Now set target and check that the linear ramp works as expected.
231 rampLinear.setTarget(finalTarget);
Phil Burkbb78a732018-03-28 15:37:19 -0700232 int32_t numRead = sinkFloat.read(output, numOutput);
Robert Wuc3278fe2021-06-23 16:51:40 +0000233 const float incrementSize = (finalTarget - initialTarget) / rampSize;
Phil Burkbb78a732018-03-28 15:37:19 -0700234 ASSERT_EQ(numOutput, numRead);
Robert Wuc3278fe2021-06-23 16:51:40 +0000235
Phil Burkbb78a732018-03-28 15:37:19 -0700236 int i = 0;
237 for (; i < rampSize; i++) {
Robert Wuc3278fe2021-06-23 16:51:40 +0000238 float expected = value * (initialTarget + i * incrementSize);
Phil Burkbb78a732018-03-28 15:37:19 -0700239 EXPECT_NEAR(expected, output[i], tolerance);
240 }
241 for (; i < numOutput; i++) {
Robert Wuc3278fe2021-06-23 16:51:40 +0000242 float expected = value * finalTarget;
Phil Burkbb78a732018-03-28 15:37:19 -0700243 EXPECT_NEAR(expected, output[i], tolerance);
244 }
245}
246
247// It is easiest to represent packed 24-bit data as a byte array.
248// This test will read from input, convert to float, then write
249// back to output as bytes.
250TEST(test_flowgraph, module_packed_24) {
251 static const uint8_t input[] = {0x01, 0x23, 0x45,
252 0x67, 0x89, 0xAB,
253 0xCD, 0xEF, 0x5A};
254 uint8_t output[99] = {};
255 SourceI24 sourceI24{1};
256 SinkI24 sinkI24{1};
257
258 int numInputFrames = sizeof(input) / kBytesPerI24Packed;
259 sourceI24.setData(input, numInputFrames);
260 sourceI24.output.connect(&sinkI24.input);
261
262 int32_t numRead = sinkI24.read(output, sizeof(output) / kBytesPerI24Packed);
263 ASSERT_EQ(numInputFrames, numRead);
264 for (size_t i = 0; i < sizeof(input); i++) {
265 EXPECT_EQ(input[i], output[i]);
266 }
267}
268
269TEST(test_flowgraph, module_clip_to_range) {
270 constexpr float myMin = -2.0f;
271 constexpr float myMax = 1.5f;
272
273 static const float input[] = {-9.7, 0.5f, -0.25, 1.0f, 12.3};
274 static const float expected[] = {myMin, 0.5f, -0.25, 1.0f, myMax};
275 float output[100];
276 SourceFloat sourceFloat{1};
277 ClipToRange clipper{1};
278 SinkFloat sinkFloat{1};
279
280 int numInputFrames = sizeof(input) / sizeof(input[0]);
281 sourceFloat.setData(input, numInputFrames);
282
283 clipper.setMinimum(myMin);
284 clipper.setMaximum(myMax);
285
286 sourceFloat.output.connect(&clipper.input);
287 clipper.output.connect(&sinkFloat.input);
288
289 int numOutputFrames = sizeof(output) / sizeof(output[0]);
290 int32_t numRead = sinkFloat.read(output, numOutputFrames);
291 ASSERT_EQ(numInputFrames, numRead);
292 constexpr float tolerance = 0.000001f; // arbitrary
293 for (int i = 0; i < numRead; i++) {
294 EXPECT_NEAR(expected[i], output[i], tolerance);
295 }
296}
Robert Wud7400832021-12-04 01:11:19 +0000297
298TEST(test_flowgraph, module_mono_blend) {
299 // Two channel to two channel with 3 inputs and outputs.
300 constexpr int numChannels = 2;
301 constexpr int numFrames = 3;
302
303 static const float input[] = {-0.7, 0.5, -0.25, 1.25, 1000, 2000};
304 static const float expected[] = {-0.1, -0.1, 0.5, 0.5, 1500, 1500};
305 float output[100];
306 SourceFloat sourceFloat{numChannels};
307 MonoBlend monoBlend{numChannels};
308 SinkFloat sinkFloat{numChannels};
309
310 sourceFloat.setData(input, numFrames);
311
312 sourceFloat.output.connect(&monoBlend.input);
313 monoBlend.output.connect(&sinkFloat.input);
314
315 int32_t numRead = sinkFloat.read(output, numFrames);
316 ASSERT_EQ(numRead, numFrames);
317 constexpr float tolerance = 0.000001f; // arbitrary
318 for (int i = 0; i < numRead; i++) {
319 EXPECT_NEAR(expected[i], output[i], tolerance);
320 }
321}
322
Robert Wu67375a12022-08-25 22:04:29 +0000323TEST(test_flowgraph, module_limiter) {
324 constexpr int kNumSamples = 101;
325 constexpr float kLastSample = 3.0f;
326 constexpr float kFirstSample = -kLastSample;
327 constexpr float kDeltaBetweenSamples = (kLastSample - kFirstSample) / (kNumSamples - 1);
328 constexpr float kTolerance = 0.00001f;
329
330 float input[kNumSamples];
331 float output[kNumSamples];
332 SourceFloat sourceFloat{1};
333 Limiter limiter{1};
334 SinkFloat sinkFloat{1};
335
336 for (int i = 0; i < kNumSamples; i++) {
337 input[i] = kFirstSample + i * kDeltaBetweenSamples;
338 }
339
340 const int numInputFrames = std::size(input);
341 sourceFloat.setData(input, numInputFrames);
342
343 sourceFloat.output.connect(&limiter.input);
344 limiter.output.connect(&sinkFloat.input);
345
346 const int numOutputFrames = std::size(output);
347 int32_t numRead = sinkFloat.read(output, numOutputFrames);
348 ASSERT_EQ(numInputFrames, numRead);
349
350 for (int i = 0; i < numRead; i++) {
351 // limiter must be symmetric wrt 0.
352 EXPECT_NEAR(output[i], -output[kNumSamples - i - 1], kTolerance);
353 if (i > 0) {
354 EXPECT_GE(output[i], output[i - 1]); // limiter must be monotonic
355 }
356 if (input[i] == 0.f) {
357 EXPECT_EQ(0.f, output[i]);
358 } else if (input[i] > 0.0f) {
359 EXPECT_GE(output[i], 0.0f);
360 EXPECT_LE(output[i], M_SQRT2); // limiter actually limits
361 EXPECT_LE(output[i], input[i]); // a limiter, gain <= 1
362 } else {
363 EXPECT_LE(output[i], 0.0f);
364 EXPECT_GE(output[i], -M_SQRT2); // limiter actually limits
365 EXPECT_GE(output[i], input[i]); // a limiter, gain <= 1
366 }
367 if (-1.f <= input[i] && input[i] <= 1.f) {
368 EXPECT_EQ(input[i], output[i]);
369 }
370 }
371}
372
373TEST(test_flowgraph, module_limiter_nan) {
374 constexpr int kArbitraryOutputSize = 100;
375 static const float input[] = {NAN, 0.5f, NAN, NAN, -10.0f, NAN};
376 static const float expected[] = {0.0f, 0.5f, 0.5f, 0.5f, -M_SQRT2, -M_SQRT2};
377 constexpr float tolerance = 0.00001f;
378 float output[kArbitraryOutputSize];
379 SourceFloat sourceFloat{1};
380 Limiter limiter{1};
381 SinkFloat sinkFloat{1};
382
383 const int numInputFrames = std::size(input);
384 sourceFloat.setData(input, numInputFrames);
385
386 sourceFloat.output.connect(&limiter.input);
387 limiter.output.connect(&sinkFloat.input);
388
389 const int numOutputFrames = std::size(output);
390 int32_t numRead = sinkFloat.read(output, numOutputFrames);
391 ASSERT_EQ(numInputFrames, numRead);
392
393 for (int i = 0; i < numRead; i++) {
394 EXPECT_NEAR(expected[i], output[i], tolerance);
395 }
396}