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);