AAudio: add a limiter instead of clipping audio

AAudio currently clips audio above sqrt(2) and below -sqrt(2).
Instead, for a smooth transition, a polynomial spline should be used.

Bug: 17914011
Test: atest test_flowgraph
Test: atest AAudioTests
Test: Test Output in OboeTester
Change-Id: Ia48537c9c914b71f6928adfc2470f17b3108e5d5
diff --git a/media/libaaudio/src/Android.bp b/media/libaaudio/src/Android.bp
index 363d219..1eba24d 100644
--- a/media/libaaudio/src/Android.bp
+++ b/media/libaaudio/src/Android.bp
@@ -209,6 +209,7 @@
         "flowgraph/ChannelCountConverter.cpp",
         "flowgraph/ClipToRange.cpp",
         "flowgraph/FlowGraphNode.cpp",
+        "flowgraph/Limiter.cpp",
         "flowgraph/ManyToMultiConverter.cpp",
         "flowgraph/MonoBlend.cpp",
         "flowgraph/MonoToMultiConverter.cpp",
diff --git a/media/libaaudio/src/client/AAudioFlowGraph.cpp b/media/libaaudio/src/client/AAudioFlowGraph.cpp
index 2ed3e3c..5444565 100644
--- a/media/libaaudio/src/client/AAudioFlowGraph.cpp
+++ b/media/libaaudio/src/client/AAudioFlowGraph.cpp
@@ -20,7 +20,7 @@
 
 #include "AAudioFlowGraph.h"
 
-#include <flowgraph/ClipToRange.h>
+#include <flowgraph/Limiter.h>
 #include <flowgraph/ManyToMultiConverter.h>
 #include <flowgraph/MonoBlend.h>
 #include <flowgraph/MonoToMultiConverter.h>
@@ -78,11 +78,11 @@
     }
 
     // For a pure float graph, there is chance that the data range may be very large.
-    // So we should clip to a reasonable value that allows a little headroom.
+    // So we should limit to a reasonable value that allows a little headroom.
     if (sourceFormat == AUDIO_FORMAT_PCM_FLOAT && sinkFormat == AUDIO_FORMAT_PCM_FLOAT) {
-        mClipper = std::make_unique<ClipToRange>(sourceChannelCount);
-        lastOutput->connect(&mClipper->input);
-        lastOutput = &mClipper->output;
+        mLimiter = std::make_unique<Limiter>(sourceChannelCount);
+        lastOutput->connect(&mLimiter->input);
+        lastOutput = &mLimiter->output;
     }
 
     // Expand the number of channels if required.
diff --git a/media/libaaudio/src/client/AAudioFlowGraph.h b/media/libaaudio/src/client/AAudioFlowGraph.h
index 602c17f..35fef37 100644
--- a/media/libaaudio/src/client/AAudioFlowGraph.h
+++ b/media/libaaudio/src/client/AAudioFlowGraph.h
@@ -24,7 +24,7 @@
 
 #include <aaudio/AAudio.h>
 #include <audio_utils/Balance.h>
-#include <flowgraph/ClipToRange.h>
+#include <flowgraph/Limiter.h>
 #include <flowgraph/ManyToMultiConverter.h>
 #include <flowgraph/MonoBlend.h>
 #include <flowgraph/MonoToMultiConverter.h>
@@ -74,7 +74,7 @@
 private:
     std::unique_ptr<FLOWGRAPH_OUTER_NAMESPACE::flowgraph::FlowGraphSourceBuffered> mSource;
     std::unique_ptr<FLOWGRAPH_OUTER_NAMESPACE::flowgraph::MonoBlend> mMonoBlend;
-    std::unique_ptr<FLOWGRAPH_OUTER_NAMESPACE::flowgraph::ClipToRange> mClipper;
+    std::unique_ptr<FLOWGRAPH_OUTER_NAMESPACE::flowgraph::Limiter> mLimiter;
     std::unique_ptr<FLOWGRAPH_OUTER_NAMESPACE::flowgraph::MonoToMultiConverter> mChannelConverter;
     std::unique_ptr<FLOWGRAPH_OUTER_NAMESPACE::flowgraph::ManyToMultiConverter>
             mManyToMultiConverter;
diff --git a/media/libaaudio/src/flowgraph/Limiter.cpp b/media/libaaudio/src/flowgraph/Limiter.cpp
new file mode 100644
index 0000000..def905a
--- /dev/null
+++ b/media/libaaudio/src/flowgraph/Limiter.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <math.h>
+#include <unistd.h>
+#include "FlowGraphNode.h"
+#include "Limiter.h"
+
+using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
+
+Limiter::Limiter(int32_t channelCount)
+        : FlowGraphFilter(channelCount) {
+}
+
+int32_t Limiter::onProcess(int32_t numFrames) {
+    const float *inputBuffer = input.getBuffer();
+    float *outputBuffer = output.getBuffer();
+
+    int32_t numSamples = numFrames * output.getSamplesPerFrame();
+
+    // Cache the last valid output to reduce memory read/write
+    float lastValidOutput = mLastValidOutput;
+
+    for (int32_t i = 0; i < numSamples; i++) {
+        // Use the previous output if the input is NaN
+        if (!isnan(*inputBuffer)) {
+            lastValidOutput = processFloat(*inputBuffer);
+        }
+        inputBuffer++;
+        *outputBuffer++ = lastValidOutput;
+    }
+    mLastValidOutput = lastValidOutput;
+
+    return numFrames;
+}
+
+float Limiter::processFloat(float in)
+{
+    float in_abs = fabsf(in);
+    if (in_abs <= 1) {
+        return in;
+    }
+    float out;
+    if (in_abs < kXWhenYis3Decibels) {
+        out = (kPolynomialSplineA * in_abs + kPolynomialSplineB) * in_abs + kPolynomialSplineC;
+    } else {
+        out = M_SQRT2;
+    }
+    if (in < 0) {
+        out = -out;
+    }
+    return out;
+}
diff --git a/media/libaaudio/src/flowgraph/Limiter.h b/media/libaaudio/src/flowgraph/Limiter.h
new file mode 100644
index 0000000..393a7bf
--- /dev/null
+++ b/media/libaaudio/src/flowgraph/Limiter.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FLOWGRAPH_LIMITER_H
+#define FLOWGRAPH_LIMITER_H
+
+#include <atomic>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "FlowGraphNode.h"
+
+namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
+
+class Limiter : public FlowGraphFilter {
+public:
+    explicit Limiter(int32_t channelCount);
+
+    int32_t onProcess(int32_t numFrames) override;
+
+    const char *getName() override {
+        return "Limiter";
+    }
+
+private:
+    // These numbers are based on a polynomial spline for a quadratic solution Ax^2 + Bx + C
+    // The range is up to 3 dB, (10^(3/20)), to match AudioTrack for float data.
+    static constexpr float kPolynomialSplineA = -0.6035533905; // -(1+sqrt(2))/4
+    static constexpr float kPolynomialSplineB = 2.2071067811; // (3+sqrt(2))/2
+    static constexpr float kPolynomialSplineC = -0.6035533905; // -(1+sqrt(2))/4
+    static constexpr float kXWhenYis3Decibels = 1.8284271247; // -1+2sqrt(2)
+
+    /**
+     * Process an input based on the following:
+     * If between -1 and 1, return the input value.
+     * If above kXWhenYis3Decibels, return sqrt(2).
+     * If below -kXWhenYis3Decibels, return -sqrt(2).
+     * If between 1 and kXWhenYis3Decibels, use a quadratic spline (Ax^2 + Bx + C).
+     * If between -kXWhenYis3Decibels and -1, use the absolute value for the spline and flip it.
+     * The derivative of the spline is 1 at 1 and 0 at kXWhenYis3Decibels.
+     * This way, the graph is both continuous and differentiable.
+     */
+    float processFloat(float in);
+
+    // Use the previous valid output for NaN inputs
+    float mLastValidOutput = 0.0f;
+};
+
+} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
+
+#endif //FLOWGRAPH_LIMITER_H