Implement gain for PCM types in default audio HAL

Implemented gain for all supported PCM types in default audio HAL,
and added unit tests.

Bug: 336370745
Test: atest audio_alsa_utils_tests
Change-Id: I02c062c8f10ec8cc0b78174acc52cf5dd7cbdcd0
diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp
index 73d7626..967d0b9 100644
--- a/audio/aidl/default/Android.bp
+++ b/audio/aidl/default/Android.bp
@@ -200,6 +200,47 @@
     test_suites: ["general-tests"],
 }
 
+cc_test {
+    name: "audio_alsa_utils_tests",
+    vendor_available: true,
+    defaults: [
+        "latest_android_media_audio_common_types_ndk_static",
+        "latest_android_hardware_audio_core_ndk_static",
+    ],
+    static_libs: [
+        "libalsautilsv2",
+        "libtinyalsav2",
+    ],
+    shared_libs: [
+        "libaudio_aidl_conversion_common_ndk",
+        "libaudioaidlcommon",
+        "libaudioutils",
+        "libbase",
+        "libbinder_ndk",
+        "libcutils",
+        "libfmq",
+        "libmedia_helper",
+        "libstagefright_foundation",
+        "libutils",
+    ],
+    header_libs: [
+        "libaudio_system_headers",
+        "libaudioaidl_headers",
+    ],
+    srcs: [
+        "alsa/Utils.cpp",
+        "tests/AlsaUtilsTest.cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-Wthread-safety",
+        "-DBACKEND_NDK",
+    ],
+    test_suites: ["general-tests"],
+}
+
 cc_defaults {
     name: "aidlaudioeffectservice_defaults",
     defaults: [
diff --git a/audio/aidl/default/alsa/Utils.cpp b/audio/aidl/default/alsa/Utils.cpp
index 10374f2..77e4f65 100644
--- a/audio/aidl/default/alsa/Utils.cpp
+++ b/audio/aidl/default/alsa/Utils.cpp
@@ -39,6 +39,8 @@
 
 namespace aidl::android::hardware::audio::core::alsa {
 
+const float kUnityGainFloat = 1.0f;
+
 DeviceProxy::DeviceProxy() : mProfile(nullptr), mProxy(nullptr, alsaProxyDeleter) {}
 
 DeviceProxy::DeviceProxy(const DeviceProfile& deviceProfile)
@@ -140,7 +142,6 @@
 
 const AudioFormatDescToPcmFormatMap& getAudioFormatDescriptorToPcmFormatMap() {
     static const AudioFormatDescToPcmFormatMap formatDescToPcmFormatMap = {
-            {make_AudioFormatDescription(PcmType::UINT_8_BIT), PCM_FORMAT_S8},
             {make_AudioFormatDescription(PcmType::INT_16_BIT), PCM_FORMAT_S16_LE},
             {make_AudioFormatDescription(PcmType::FIXED_Q_8_24), PCM_FORMAT_S24_LE},
             {make_AudioFormatDescription(PcmType::INT_24_BIT), PCM_FORMAT_S24_3LE},
@@ -165,6 +166,92 @@
     return pcmFormatToFormatDescMap;
 }
 
+void applyGainToInt16Buffer(void* buffer, const size_t bufferSizeBytes, const float gain,
+                            int channelCount) {
+    const uint16_t unityGainQ4_12 = u4_12_from_float(kUnityGainFloat);
+    const uint16_t vl = u4_12_from_float(gain);
+    const uint32_t vrl = (vl << 16) | vl;
+    int numFrames = 0;
+    if (channelCount == 2) {
+        numFrames = bufferSizeBytes / sizeof(uint32_t);
+        if (numFrames == 0) {
+            return;
+        }
+        uint32_t* intBuffer = (uint32_t*)buffer;
+        if (CC_UNLIKELY(vl > unityGainQ4_12)) {
+            do {
+                int32_t l = mulRL(1, *intBuffer, vrl) >> 12;
+                int32_t r = mulRL(0, *intBuffer, vrl) >> 12;
+                l = clamp16(l);
+                r = clamp16(r);
+                *intBuffer++ = (r << 16) | (l & 0xFFFF);
+            } while (--numFrames);
+        } else {
+            do {
+                int32_t l = mulRL(1, *intBuffer, vrl) >> 12;
+                int32_t r = mulRL(0, *intBuffer, vrl) >> 12;
+                *intBuffer++ = (r << 16) | (l & 0xFFFF);
+            } while (--numFrames);
+        }
+    } else {
+        numFrames = bufferSizeBytes / sizeof(uint16_t);
+        if (numFrames == 0) {
+            return;
+        }
+        int16_t* intBuffer = (int16_t*)buffer;
+        if (CC_UNLIKELY(vl > unityGainQ4_12)) {
+            do {
+                int32_t mono = mul(*intBuffer, static_cast<int16_t>(vl)) >> 12;
+                *intBuffer++ = clamp16(mono);
+            } while (--numFrames);
+        } else {
+            do {
+                int32_t mono = mul(*intBuffer, static_cast<int16_t>(vl)) >> 12;
+                *intBuffer++ = static_cast<int16_t>(mono & 0xFFFF);
+            } while (--numFrames);
+        }
+    }
+}
+
+void applyGainToInt32Buffer(int32_t* typedBuffer, const size_t bufferSizeBytes, const float gain) {
+    int numSamples = bufferSizeBytes / sizeof(int32_t);
+    if (numSamples == 0) {
+        return;
+    }
+    if (CC_UNLIKELY(gain > kUnityGainFloat)) {
+        do {
+            float multiplied = (*typedBuffer) * gain;
+            if (multiplied > INT32_MAX) {
+                *typedBuffer++ = INT32_MAX;
+            } else if (multiplied < INT32_MIN) {
+                *typedBuffer++ = INT32_MIN;
+            } else {
+                *typedBuffer++ = multiplied;
+            }
+        } while (--numSamples);
+    } else {
+        do {
+            *typedBuffer++ = (*typedBuffer) * gain;
+        } while (--numSamples);
+    }
+}
+
+void applyGainToFloatBuffer(float* floatBuffer, const size_t bufferSizeBytes, const float gain) {
+    int numSamples = bufferSizeBytes / sizeof(float);
+    if (numSamples == 0) {
+        return;
+    }
+    if (CC_UNLIKELY(gain > kUnityGainFloat)) {
+        do {
+            *floatBuffer++ = std::clamp((*floatBuffer) * gain, -kUnityGainFloat, kUnityGainFloat);
+        } while (--numSamples);
+    } else {
+        do {
+            *floatBuffer++ = (*floatBuffer) * gain;
+        } while (--numSamples);
+    }
+}
+
 }  // namespace
 
 std::ostream& operator<<(std::ostream& os, const DeviceProfile& device) {
@@ -345,7 +432,7 @@
     return findValueOrDefault(getAudioFormatDescriptorToPcmFormatMap(), aidl, PCM_FORMAT_INVALID);
 }
 
-void applyGain(void* buffer, float gain, size_t bytesToTransfer, enum pcm_format pcmFormat,
+void applyGain(void* buffer, float gain, size_t bufferSizeBytes, enum pcm_format pcmFormat,
                int channelCount) {
     if (channelCount != 1 && channelCount != 2) {
         LOG(WARNING) << __func__ << ": unsupported channel count " << channelCount;
@@ -355,56 +442,37 @@
         LOG(WARNING) << __func__ << ": unsupported pcm format " << pcmFormat;
         return;
     }
-    const float unityGainFloat = 1.0f;
-    if (std::abs(gain - unityGainFloat) < 1e-6) {
+    if (std::abs(gain - kUnityGainFloat) < 1e-6) {
         return;
     }
-    int numFrames;
     switch (pcmFormat) {
-        case PCM_FORMAT_S16_LE: {
-            const uint16_t unityGainQ4_12 = u4_12_from_float(unityGainFloat);
-            const uint16_t vl = u4_12_from_float(gain);
-            const uint32_t vrl = (vl << 16) | vl;
-            if (channelCount == 2) {
-                numFrames = bytesToTransfer / sizeof(uint32_t);
-                uint32_t* intBuffer = (uint32_t*)buffer;
-                if (CC_UNLIKELY(vl > unityGainQ4_12)) {
-                    // volume is boosted, so we might need to clamp even though
-                    // we process only one track.
-                    do {
-                        int32_t l = mulRL(1, *intBuffer, vrl) >> 12;
-                        int32_t r = mulRL(0, *intBuffer, vrl) >> 12;
-                        l = clamp16(l);
-                        r = clamp16(r);
-                        *intBuffer++ = (r << 16) | (l & 0xFFFF);
-                    } while (--numFrames);
-                } else {
-                    do {
-                        int32_t l = mulRL(1, *intBuffer, vrl) >> 12;
-                        int32_t r = mulRL(0, *intBuffer, vrl) >> 12;
-                        *intBuffer++ = (r << 16) | (l & 0xFFFF);
-                    } while (--numFrames);
-                }
-            } else {
-                numFrames = bytesToTransfer / sizeof(uint16_t);
-                int16_t* intBuffer = (int16_t*)buffer;
-                if (CC_UNLIKELY(vl > unityGainQ4_12)) {
-                    // volume is boosted, so we might need to clamp even though
-                    // we process only one track.
-                    do {
-                        int32_t mono = mulRL(1, *intBuffer, vrl) >> 12;
-                        *intBuffer++ = clamp16(mono);
-                    } while (--numFrames);
-                } else {
-                    do {
-                        int32_t mono = mulRL(1, *intBuffer, vrl) >> 12;
-                        *intBuffer++ = static_cast<int16_t>(mono & 0xFFFF);
-                    } while (--numFrames);
-                }
+        case PCM_FORMAT_S16_LE:
+            applyGainToInt16Buffer(buffer, bufferSizeBytes, gain, channelCount);
+            break;
+        case PCM_FORMAT_FLOAT_LE: {
+            float* floatBuffer = (float*)buffer;
+            applyGainToFloatBuffer(floatBuffer, bufferSizeBytes, gain);
+        } break;
+        case PCM_FORMAT_S24_LE:
+            // PCM_FORMAT_S24_LE buffer is composed of signed fixed-point 32-bit Q8.23 data with
+            // min and max limits of the same bit representation as min and max limits of
+            // PCM_FORMAT_S32_LE buffer.
+        case PCM_FORMAT_S32_LE: {
+            int32_t* typedBuffer = (int32_t*)buffer;
+            applyGainToInt32Buffer(typedBuffer, bufferSizeBytes, gain);
+        } break;
+        case PCM_FORMAT_S24_3LE: {
+            int numSamples = bufferSizeBytes / (sizeof(uint8_t) * 3);
+            if (numSamples == 0) {
+                return;
             }
+            std::unique_ptr<int32_t[]> typedBuffer(new int32_t[numSamples]);
+            memcpy_to_i32_from_p24(typedBuffer.get(), (uint8_t*)buffer, numSamples);
+            applyGainToInt32Buffer(typedBuffer.get(), numSamples * sizeof(int32_t), gain);
+            memcpy_to_p24_from_i32((uint8_t*)buffer, typedBuffer.get(), numSamples);
         } break;
         default:
-            // TODO(336370745): Implement gain for other supported formats
+            LOG(FATAL) << __func__ << ": unsupported pcm format " << pcmFormat;
             break;
     }
 }
diff --git a/audio/aidl/default/tests/AlsaUtilsTest.cpp b/audio/aidl/default/tests/AlsaUtilsTest.cpp
new file mode 100644
index 0000000..226eea0
--- /dev/null
+++ b/audio/aidl/default/tests/AlsaUtilsTest.cpp
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#define LOG_TAG "AlsaUtilsTest"
+
+#include <alsa/Utils.h>
+#include <android-base/macros.h>
+#include <audio_utils/primitives.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+extern "C" {
+#include <tinyalsa/pcm.h>
+}
+
+namespace alsa = ::aidl::android::hardware::audio::core::alsa;
+
+namespace {
+
+const static constexpr float kInt16tTolerance = 4;
+const static constexpr float kIntTolerance = 1;
+const static constexpr float kFloatTolerance = 1e-4;
+const static constexpr float kUnityGain = 1;
+const static constexpr int32_t kInt24Min = -(1 << 23);
+const static constexpr int32_t kInt24Max = (1 << 23) - 1;
+const static constexpr float kFloatMin = -1;
+const static constexpr float kFloatMax = 1;
+const static int32_t kQ8_23Min = 0x80000000;
+const static int32_t kQ8_23Max = 0x7FFFFFFF;
+const static std::vector<int16_t> kInt16Buffer = {10000,     100,   0,    INT16_MAX,
+                                                  INT16_MIN, -2500, 1000, -5800};
+const static std::vector<float> kFloatBuffer = {0.5, -0.6, kFloatMin, 0.01, kFloatMax, 0.0};
+const static std::vector<int32_t> kInt32Buffer = {100, 0, 8000, INT32_MAX, INT32_MIN, -300};
+const static std::vector<int32_t> kQ8_23Buffer = {
+        kQ8_23Min, kQ8_23Max, 0x00000000, 0x00000001, 0x00400000, static_cast<int32_t>(0xFFD33333)};
+const static std::vector<int32_t> kInt24Buffer = {200, 10, -100, 0, kInt24Min, kInt24Max};
+
+template <typename T>
+void* CopyToBuffer(int& bytesToTransfer, std::vector<T>& destBuffer,
+                   const std::vector<T>& srcBuffer) {
+    bytesToTransfer = srcBuffer.size() * sizeof(T);
+    destBuffer = srcBuffer;
+    return destBuffer.data();
+}
+
+template <typename T>
+void VerifyTypedBufferResults(const std::vector<T>& bufferWithGain, const std::vector<T>& srcBuffer,
+                              float gain, float tolerance) {
+    for (size_t i = 0; i < srcBuffer.size(); i++) {
+        EXPECT_NEAR(srcBuffer[i] * gain, static_cast<float>(bufferWithGain[i]), tolerance);
+    }
+}
+
+template <typename T>
+void VerifyTypedBufferResultsWithClamp(const std::vector<T>& bufferWithGain,
+                                       const std::vector<T>& srcBuffer, float gain, float tolerance,
+                                       T minValue, T maxValue) {
+    for (size_t i = 0; i < srcBuffer.size(); i++) {
+        float expectedResult = std::clamp(srcBuffer[i] * gain, static_cast<float>(minValue),
+                                          static_cast<float>(maxValue));
+        EXPECT_NEAR(expectedResult, static_cast<float>(bufferWithGain[i]), tolerance);
+    }
+}
+
+}  // namespace
+
+using ApplyGainTestParameters = std::tuple<pcm_format, int, float>;
+enum { INDEX_PCM_FORMAT, INDEX_CHANNEL_COUNT, INDEX_GAIN };
+
+class ApplyGainTest : public ::testing::TestWithParam<ApplyGainTestParameters> {
+  protected:
+    void SetUp() override;
+    void VerifyBufferResult(const pcm_format pcmFormat, const float gain);
+    void VerifyBufferResultWithClamp(const pcm_format pcmFormat, const float gain);
+
+    pcm_format mPcmFormat;
+    int mBufferSizeBytes;
+    void* mBuffer;
+
+  private:
+    std::vector<int16_t> mInt16BufferToConvert;
+    std::vector<float> mFloatBufferToConvert;
+    std::vector<int32_t> mInt32BufferToConvert;
+    std::vector<int32_t> mQ8_23BufferToConvert;
+    std::vector<int32_t> mInt24BufferToConvert;
+};
+
+void ApplyGainTest::SetUp() {
+    mPcmFormat = std::get<INDEX_PCM_FORMAT>(GetParam());
+    switch (mPcmFormat) {
+        case PCM_FORMAT_S16_LE:
+            mBuffer = CopyToBuffer(mBufferSizeBytes, mInt16BufferToConvert, kInt16Buffer);
+            break;
+        case PCM_FORMAT_FLOAT_LE:
+            mBuffer = CopyToBuffer(mBufferSizeBytes, mFloatBufferToConvert, kFloatBuffer);
+            break;
+        case PCM_FORMAT_S32_LE:
+            mBuffer = CopyToBuffer(mBufferSizeBytes, mInt32BufferToConvert, kInt32Buffer);
+            break;
+        case PCM_FORMAT_S24_LE:
+            mBuffer = CopyToBuffer(mBufferSizeBytes, mQ8_23BufferToConvert, kQ8_23Buffer);
+            break;
+        case PCM_FORMAT_S24_3LE: {
+            std::vector<int32_t> original32BitBuffer(kInt24Buffer.begin(), kInt24Buffer.end());
+            for (auto& val : original32BitBuffer) {
+                val <<= 8;
+            }
+            mInt24BufferToConvert = std::vector<int32_t>(kInt24Buffer.size());
+            mBufferSizeBytes = kInt24Buffer.size() * 3 * sizeof(uint8_t);
+            memcpy_to_p24_from_i32(reinterpret_cast<uint8_t*>(mInt24BufferToConvert.data()),
+                                   original32BitBuffer.data(), kInt24Buffer.size());
+            mBuffer = mInt24BufferToConvert.data();
+        } break;
+        default:
+            FAIL() << "Unsupported pcm format: " << mPcmFormat;
+            return;
+    }
+}
+
+void ApplyGainTest::VerifyBufferResult(const pcm_format pcmFormat, const float gain) {
+    switch (pcmFormat) {
+        case PCM_FORMAT_S16_LE:
+            VerifyTypedBufferResults(mInt16BufferToConvert, kInt16Buffer, gain, kInt16tTolerance);
+            break;
+        case PCM_FORMAT_FLOAT_LE:
+            VerifyTypedBufferResults(mFloatBufferToConvert, kFloatBuffer, gain, kFloatTolerance);
+            break;
+        case PCM_FORMAT_S32_LE:
+            VerifyTypedBufferResults(mInt32BufferToConvert, kInt32Buffer, gain, kIntTolerance);
+            break;
+        case PCM_FORMAT_S24_LE: {
+            for (size_t i = 0; i < kQ8_23Buffer.size(); i++) {
+                EXPECT_NEAR(float_from_q8_23(kQ8_23Buffer[i]) * gain,
+                            static_cast<float>(float_from_q8_23(mQ8_23BufferToConvert[i])),
+                            kFloatTolerance);
+            }
+        } break;
+        case PCM_FORMAT_S24_3LE: {
+            size_t bufferSize = kInt24Buffer.size();
+            std::vector<int32_t> result32BitBuffer(bufferSize);
+            memcpy_to_i32_from_p24(result32BitBuffer.data(),
+                                   reinterpret_cast<uint8_t*>(mInt24BufferToConvert.data()),
+                                   bufferSize);
+            for (size_t i = 0; i < bufferSize; i++) {
+                EXPECT_NEAR(kInt24Buffer[i] * gain, result32BitBuffer[i] >> 8, kIntTolerance);
+            }
+        } break;
+        default:
+            return;
+    }
+}
+
+void ApplyGainTest::VerifyBufferResultWithClamp(const pcm_format pcmFormat, const float gain) {
+    switch (pcmFormat) {
+        case PCM_FORMAT_S16_LE:
+            VerifyTypedBufferResultsWithClamp(mInt16BufferToConvert, kInt16Buffer, gain,
+                                              kInt16tTolerance, static_cast<int16_t>(INT16_MIN),
+                                              static_cast<int16_t>(INT16_MAX));
+            break;
+        case PCM_FORMAT_FLOAT_LE:
+            VerifyTypedBufferResultsWithClamp(mFloatBufferToConvert, kFloatBuffer, gain,
+                                              kFloatTolerance, kFloatMin, kFloatMax);
+            break;
+        case PCM_FORMAT_S32_LE:
+            VerifyTypedBufferResultsWithClamp(mInt32BufferToConvert, kInt32Buffer, gain,
+                                              kIntTolerance, INT32_MIN, INT32_MAX);
+            break;
+        case PCM_FORMAT_S24_LE: {
+            for (size_t i = 0; i < kQ8_23Buffer.size(); i++) {
+                float expectedResult =
+                        std::clamp(float_from_q8_23(kQ8_23Buffer[i]) * gain,
+                                   float_from_q8_23(kQ8_23Min), float_from_q8_23(kQ8_23Max));
+                EXPECT_NEAR(expectedResult,
+                            static_cast<float>(float_from_q8_23(mQ8_23BufferToConvert[i])),
+                            kFloatTolerance);
+            }
+        } break;
+        case PCM_FORMAT_S24_3LE: {
+            size_t bufferSize = kInt24Buffer.size();
+            std::vector<int32_t> result32BitBuffer(bufferSize);
+            memcpy_to_i32_from_p24(result32BitBuffer.data(),
+                                   reinterpret_cast<uint8_t*>(mInt24BufferToConvert.data()),
+                                   bufferSize);
+            for (size_t i = 0; i < bufferSize; i++) {
+                result32BitBuffer[i] >>= 8;
+            }
+            VerifyTypedBufferResultsWithClamp(result32BitBuffer, kInt24Buffer, gain, kIntTolerance,
+                                              kInt24Min, kInt24Max);
+        } break;
+        default:
+            return;
+    }
+}
+
+TEST_P(ApplyGainTest, ApplyGain) {
+    float gain = std::get<INDEX_GAIN>(GetParam());
+    int channelCount = std::get<INDEX_CHANNEL_COUNT>(GetParam());
+
+    alsa::applyGain(mBuffer, gain, mBufferSizeBytes, mPcmFormat, channelCount);
+
+    if (gain <= kUnityGain) {
+        VerifyBufferResult(mPcmFormat, gain);
+    } else {
+        VerifyBufferResultWithClamp(mPcmFormat, gain);
+    }
+}
+
+std::string GetApplyGainTestName(const testing::TestParamInfo<ApplyGainTestParameters>& info) {
+    std::string testNameStr;
+    switch (std::get<INDEX_PCM_FORMAT>(info.param)) {
+        case PCM_FORMAT_S16_LE:
+            testNameStr = "S16_LE";
+            break;
+        case PCM_FORMAT_FLOAT_LE:
+            testNameStr = "Float_LE";
+            break;
+        case PCM_FORMAT_S32_LE:
+            testNameStr = "S32_LE";
+            break;
+        case PCM_FORMAT_S24_LE:
+            testNameStr = "S24_LE";
+            break;
+        case PCM_FORMAT_S24_3LE:
+            testNameStr = "S24_3LE";
+            break;
+        default:
+            testNameStr = "UnsupportedPcmFormat";
+            break;
+    }
+    testNameStr += std::get<INDEX_CHANNEL_COUNT>(info.param) == 1 ? "_Mono_" : "_Stereo_";
+    testNameStr += std::get<INDEX_GAIN>(info.param) <= kUnityGain ? "WithoutClamp" : "WithClamp";
+    return testNameStr;
+}
+
+INSTANTIATE_TEST_SUITE_P(PerPcmFormat, ApplyGainTest,
+                         testing::Combine(testing::Values(PCM_FORMAT_S16_LE, PCM_FORMAT_FLOAT_LE,
+                                                          PCM_FORMAT_S32_LE, PCM_FORMAT_S24_LE,
+                                                          PCM_FORMAT_S24_3LE),
+                                          testing::Values(1, 2), testing::Values(0.6f, 1.5f)),
+                         GetApplyGainTestName);