Loading media/libeffects/loudness/Android.bp +3 −1 Original line number Diff line number Diff line Loading @@ -18,7 +18,7 @@ license { ], } cc_library_shared { cc_library { name: "libldnhncr", vendor: true, Loading @@ -36,6 +36,7 @@ cc_library_shared { ], shared_libs: [ "libaudioutils", "libcutils", "liblog", ], Loading Loading @@ -64,6 +65,7 @@ cc_library_shared { "-Wthread-safety", ], shared_libs: [ "libaudioutils", "libcutils", "liblog", ], Loading media/libeffects/loudness/EffectLoudnessEnhancer.cpp +0 −40 Original line number Diff line number Diff line Loading @@ -30,26 +30,8 @@ #include <audio_effects/effect_loudnessenhancer.h> #include "dsp/core/dynamic_range_compression.h" // BUILD_FLOAT targets building a float effect instead of the legacy int16_t effect. #define BUILD_FLOAT #ifdef BUILD_FLOAT static constexpr audio_format_t kProcessFormat = AUDIO_FORMAT_PCM_FLOAT; #else static constexpr audio_format_t kProcessFormat = AUDIO_FORMAT_PCM_16_BIT; static inline int16_t clamp16(int32_t sample) { if ((sample>>15) ^ (sample>>31)) sample = 0x7FFF ^ (sample>>31); return sample; } #endif // BUILD_FLOAT extern "C" { // effect_handle_t interface implementation for LE effect Loading Loading @@ -297,33 +279,20 @@ int LE_process( //ALOGV("LE about to process %d samples", inBuffer->frameCount); uint16_t inIdx; #ifdef BUILD_FLOAT constexpr float scale = 1 << 15; // power of 2 is lossless conversion to int16_t range constexpr float inverseScale = 1.f / scale; const float inputAmp = pow(10, pContext->mTargetGainmB/2000.0f) * scale; #else float inputAmp = pow(10, pContext->mTargetGainmB/2000.0f); #endif float leftSample, rightSample; for (inIdx = 0 ; inIdx < inBuffer->frameCount ; inIdx++) { // makeup gain is applied on the input of the compressor #ifdef BUILD_FLOAT leftSample = inputAmp * inBuffer->f32[2*inIdx]; rightSample = inputAmp * inBuffer->f32[2*inIdx +1]; pContext->mCompressor->Compress(&leftSample, &rightSample); inBuffer->f32[2*inIdx] = leftSample * inverseScale; inBuffer->f32[2*inIdx +1] = rightSample * inverseScale; #else leftSample = inputAmp * (float)inBuffer->s16[2*inIdx]; rightSample = inputAmp * (float)inBuffer->s16[2*inIdx +1]; pContext->mCompressor->Compress(&leftSample, &rightSample); inBuffer->s16[2*inIdx] = (int16_t) leftSample; inBuffer->s16[2*inIdx +1] = (int16_t) rightSample; #endif // BUILD_FLOAT } if (inBuffer->raw != outBuffer->raw) { #ifdef BUILD_FLOAT if (pContext->mConfig.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE) { for (size_t i = 0; i < outBuffer->frameCount*2; i++) { outBuffer->f32[i] += inBuffer->f32[i]; Loading @@ -331,15 +300,6 @@ int LE_process( } else { memcpy(outBuffer->raw, inBuffer->raw, outBuffer->frameCount * 2 * sizeof(float)); } #else if (pContext->mConfig.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE) { for (size_t i = 0; i < outBuffer->frameCount*2; i++) { outBuffer->s16[i] = clamp16(outBuffer->s16[i] + inBuffer->s16[i]); } } else { memcpy(outBuffer->raw, inBuffer->raw, outBuffer->frameCount * 2 * sizeof(int16_t)); } #endif // BUILD_FLOAT } if (pContext->mState != LOUDNESS_ENHANCER_STATE_ACTIVE) { return -ENODATA; Loading media/libeffects/loudness/tests/Android.bp 0 → 100644 +36 −0 Original line number Diff line number Diff line // Build the unit tests for loudness effect tests package { // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_av_license" // to get the below license kinds: // SPDX-license-identifier-Apache-2.0 default_applicable_licenses: ["frameworks_av_license"], } cc_test { name: "loudness_enhancer_tests", srcs: [ "loudness_enhancer_tests.cpp", ], shared_libs: [ "libbase", "liblog", ], static_libs: [ "libldnhncr", ], header_libs: [ "libaudioeffects", "libaudioutils_headers", ], include_dirs: [ "frameworks/av/media/libeffects/loudness", ], cflags: [ "-Wall", "-Werror", "-Wthread-safety", ], } media/libeffects/loudness/tests/loudness_enhancer_tests.cpp 0 → 100644 +92 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 "dsp/core/dynamic_range_compression.h" #include <audio_effects/effect_loudnessenhancer.h> #include <audio_utils/dsp_utils.h> #include <system/audio_effects/audio_effects_test.h> #include <gtest/gtest.h> using status_t = int32_t; extern audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM; effect_uuid_t loudness_uuid = {0xfa415329, 0x2034, 0x4bea, 0xb5dc, {0x5b, 0x38, 0x1c, 0x8d, 0x1e, 0x2c}}; using namespace android::audio_utils; using namespace android::effect::utils; // Android 16: // expectedEnergydB: -24.771212 energyIndB: -24.739433 // gaindB: 0.000000 measureddB: 0.000000 energyIndB: -24.739433 energyOutdB: -24.739433 // gaindB: 1.000000 measureddB: 1.000004 energyIndB: -24.739433 energyOutdB: -23.739429 // gaindB: 2.000000 measureddB: 2.000002 energyIndB: -24.739433 energyOutdB: -22.739431 // gaindB: 5.000000 measureddB: 5.000006 energyIndB: -24.739433 energyOutdB: -19.739428 // gaindB: 10.000000 measureddB: 10.000004 energyIndB: -24.739433 energyOutdB: -14.739429 // -- gain saturates below as the output approaches 0dBov. // gaindB: 20.000000 measureddB: 13.444631 energyIndB: -24.739433 energyOutdB: -11.294803 // gaindB: 50.000000 measureddB: 18.691999 energyIndB: -24.739433 energyOutdB: -6.047434 // gaindB: 100.000000 measureddB: 22.908695 energyIndB: -24.739433 energyOutdB: -1.830737 TEST(loudness_enhancer, gain_check) { effect_handle_t handle; ASSERT_EQ(0, AUDIO_EFFECT_LIBRARY_INFO_SYM.create_effect( &loudness_uuid, 0 /* sessionId */, 0 /* ioId */, &handle)); ASSERT_EQ(0, effect_enable(handle)); constexpr size_t frameCount = 1024; constexpr size_t channelCount = 2; constexpr float amplitude = 0.1; const size_t sampleCount = channelCount * frameCount; std::vector<float> originalData(sampleCount); initUniformDistribution(originalData, -amplitude, amplitude); std::vector<float> outData(sampleCount); // compute the expected energy in dB for a uniform distribution from -amplitude to amplitude. const float expectedEnergydB = energyOfUniformDistribution(-amplitude, amplitude); const float energyIndB = energy(originalData); ALOGD("%s: expectedEnergydB: %f energyIndB: %f", __func__, expectedEnergydB, energyIndB); EXPECT_NEAR(energyIndB, expectedEnergydB, 0.1); // within 0.1dB. float lastMeasuredGaindB = 0; for (int gainmB : { 0, 100, 200, 500, 1'000, 2'000, 5'000, 10'000 }) { // millibel Power ASSERT_EQ(0, effect_set_param(handle, LOUDNESS_ENHANCER_PARAM_TARGET_GAIN_MB, gainmB)); auto inData = originalData; audio_buffer_t inBuffer{ .frameCount = frameCount, .f32 = inData.data() }; audio_buffer_t outBuffer{ .frameCount = frameCount, .f32 = outData.data() }; ASSERT_EQ(0, effect_process(handle, &inBuffer, &outBuffer)); const float energyOutdB = energy(inData); const float gaindB = gainmB * 1e-2; const float measuredGaindB = energyOutdB - energyIndB; // Log our gain and power levels ALOGD("%s: gaindB: %f measureddB: %f energyIndB: %f energyOutdB: %f", __func__, gaindB, measuredGaindB, energyIndB, energyOutdB); // Gain curve testing (move to VTS)? if (gaindB == 0) { EXPECT_EQ(energyIndB, energyOutdB); } else if (energyIndB + gaindB < -10.f) { // less than -10dB from overflow, signal does not saturate. EXPECT_NEAR(gaindB, measuredGaindB, 0.1); } else { // effective gain saturates. EXPECT_LT(measuredGaindB, gaindB); // we're less than the desired gain. EXPECT_GT(measuredGaindB, lastMeasuredGaindB); // we're more than the previous gain. } lastMeasuredGaindB = measuredGaindB; } ASSERT_EQ(0, AUDIO_EFFECT_LIBRARY_INFO_SYM.release_effect(handle)); } Loading
media/libeffects/loudness/Android.bp +3 −1 Original line number Diff line number Diff line Loading @@ -18,7 +18,7 @@ license { ], } cc_library_shared { cc_library { name: "libldnhncr", vendor: true, Loading @@ -36,6 +36,7 @@ cc_library_shared { ], shared_libs: [ "libaudioutils", "libcutils", "liblog", ], Loading Loading @@ -64,6 +65,7 @@ cc_library_shared { "-Wthread-safety", ], shared_libs: [ "libaudioutils", "libcutils", "liblog", ], Loading
media/libeffects/loudness/EffectLoudnessEnhancer.cpp +0 −40 Original line number Diff line number Diff line Loading @@ -30,26 +30,8 @@ #include <audio_effects/effect_loudnessenhancer.h> #include "dsp/core/dynamic_range_compression.h" // BUILD_FLOAT targets building a float effect instead of the legacy int16_t effect. #define BUILD_FLOAT #ifdef BUILD_FLOAT static constexpr audio_format_t kProcessFormat = AUDIO_FORMAT_PCM_FLOAT; #else static constexpr audio_format_t kProcessFormat = AUDIO_FORMAT_PCM_16_BIT; static inline int16_t clamp16(int32_t sample) { if ((sample>>15) ^ (sample>>31)) sample = 0x7FFF ^ (sample>>31); return sample; } #endif // BUILD_FLOAT extern "C" { // effect_handle_t interface implementation for LE effect Loading Loading @@ -297,33 +279,20 @@ int LE_process( //ALOGV("LE about to process %d samples", inBuffer->frameCount); uint16_t inIdx; #ifdef BUILD_FLOAT constexpr float scale = 1 << 15; // power of 2 is lossless conversion to int16_t range constexpr float inverseScale = 1.f / scale; const float inputAmp = pow(10, pContext->mTargetGainmB/2000.0f) * scale; #else float inputAmp = pow(10, pContext->mTargetGainmB/2000.0f); #endif float leftSample, rightSample; for (inIdx = 0 ; inIdx < inBuffer->frameCount ; inIdx++) { // makeup gain is applied on the input of the compressor #ifdef BUILD_FLOAT leftSample = inputAmp * inBuffer->f32[2*inIdx]; rightSample = inputAmp * inBuffer->f32[2*inIdx +1]; pContext->mCompressor->Compress(&leftSample, &rightSample); inBuffer->f32[2*inIdx] = leftSample * inverseScale; inBuffer->f32[2*inIdx +1] = rightSample * inverseScale; #else leftSample = inputAmp * (float)inBuffer->s16[2*inIdx]; rightSample = inputAmp * (float)inBuffer->s16[2*inIdx +1]; pContext->mCompressor->Compress(&leftSample, &rightSample); inBuffer->s16[2*inIdx] = (int16_t) leftSample; inBuffer->s16[2*inIdx +1] = (int16_t) rightSample; #endif // BUILD_FLOAT } if (inBuffer->raw != outBuffer->raw) { #ifdef BUILD_FLOAT if (pContext->mConfig.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE) { for (size_t i = 0; i < outBuffer->frameCount*2; i++) { outBuffer->f32[i] += inBuffer->f32[i]; Loading @@ -331,15 +300,6 @@ int LE_process( } else { memcpy(outBuffer->raw, inBuffer->raw, outBuffer->frameCount * 2 * sizeof(float)); } #else if (pContext->mConfig.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE) { for (size_t i = 0; i < outBuffer->frameCount*2; i++) { outBuffer->s16[i] = clamp16(outBuffer->s16[i] + inBuffer->s16[i]); } } else { memcpy(outBuffer->raw, inBuffer->raw, outBuffer->frameCount * 2 * sizeof(int16_t)); } #endif // BUILD_FLOAT } if (pContext->mState != LOUDNESS_ENHANCER_STATE_ACTIVE) { return -ENODATA; Loading
media/libeffects/loudness/tests/Android.bp 0 → 100644 +36 −0 Original line number Diff line number Diff line // Build the unit tests for loudness effect tests package { // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_av_license" // to get the below license kinds: // SPDX-license-identifier-Apache-2.0 default_applicable_licenses: ["frameworks_av_license"], } cc_test { name: "loudness_enhancer_tests", srcs: [ "loudness_enhancer_tests.cpp", ], shared_libs: [ "libbase", "liblog", ], static_libs: [ "libldnhncr", ], header_libs: [ "libaudioeffects", "libaudioutils_headers", ], include_dirs: [ "frameworks/av/media/libeffects/loudness", ], cflags: [ "-Wall", "-Werror", "-Wthread-safety", ], }
media/libeffects/loudness/tests/loudness_enhancer_tests.cpp 0 → 100644 +92 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 "dsp/core/dynamic_range_compression.h" #include <audio_effects/effect_loudnessenhancer.h> #include <audio_utils/dsp_utils.h> #include <system/audio_effects/audio_effects_test.h> #include <gtest/gtest.h> using status_t = int32_t; extern audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM; effect_uuid_t loudness_uuid = {0xfa415329, 0x2034, 0x4bea, 0xb5dc, {0x5b, 0x38, 0x1c, 0x8d, 0x1e, 0x2c}}; using namespace android::audio_utils; using namespace android::effect::utils; // Android 16: // expectedEnergydB: -24.771212 energyIndB: -24.739433 // gaindB: 0.000000 measureddB: 0.000000 energyIndB: -24.739433 energyOutdB: -24.739433 // gaindB: 1.000000 measureddB: 1.000004 energyIndB: -24.739433 energyOutdB: -23.739429 // gaindB: 2.000000 measureddB: 2.000002 energyIndB: -24.739433 energyOutdB: -22.739431 // gaindB: 5.000000 measureddB: 5.000006 energyIndB: -24.739433 energyOutdB: -19.739428 // gaindB: 10.000000 measureddB: 10.000004 energyIndB: -24.739433 energyOutdB: -14.739429 // -- gain saturates below as the output approaches 0dBov. // gaindB: 20.000000 measureddB: 13.444631 energyIndB: -24.739433 energyOutdB: -11.294803 // gaindB: 50.000000 measureddB: 18.691999 energyIndB: -24.739433 energyOutdB: -6.047434 // gaindB: 100.000000 measureddB: 22.908695 energyIndB: -24.739433 energyOutdB: -1.830737 TEST(loudness_enhancer, gain_check) { effect_handle_t handle; ASSERT_EQ(0, AUDIO_EFFECT_LIBRARY_INFO_SYM.create_effect( &loudness_uuid, 0 /* sessionId */, 0 /* ioId */, &handle)); ASSERT_EQ(0, effect_enable(handle)); constexpr size_t frameCount = 1024; constexpr size_t channelCount = 2; constexpr float amplitude = 0.1; const size_t sampleCount = channelCount * frameCount; std::vector<float> originalData(sampleCount); initUniformDistribution(originalData, -amplitude, amplitude); std::vector<float> outData(sampleCount); // compute the expected energy in dB for a uniform distribution from -amplitude to amplitude. const float expectedEnergydB = energyOfUniformDistribution(-amplitude, amplitude); const float energyIndB = energy(originalData); ALOGD("%s: expectedEnergydB: %f energyIndB: %f", __func__, expectedEnergydB, energyIndB); EXPECT_NEAR(energyIndB, expectedEnergydB, 0.1); // within 0.1dB. float lastMeasuredGaindB = 0; for (int gainmB : { 0, 100, 200, 500, 1'000, 2'000, 5'000, 10'000 }) { // millibel Power ASSERT_EQ(0, effect_set_param(handle, LOUDNESS_ENHANCER_PARAM_TARGET_GAIN_MB, gainmB)); auto inData = originalData; audio_buffer_t inBuffer{ .frameCount = frameCount, .f32 = inData.data() }; audio_buffer_t outBuffer{ .frameCount = frameCount, .f32 = outData.data() }; ASSERT_EQ(0, effect_process(handle, &inBuffer, &outBuffer)); const float energyOutdB = energy(inData); const float gaindB = gainmB * 1e-2; const float measuredGaindB = energyOutdB - energyIndB; // Log our gain and power levels ALOGD("%s: gaindB: %f measureddB: %f energyIndB: %f energyOutdB: %f", __func__, gaindB, measuredGaindB, energyIndB, energyOutdB); // Gain curve testing (move to VTS)? if (gaindB == 0) { EXPECT_EQ(energyIndB, energyOutdB); } else if (energyIndB + gaindB < -10.f) { // less than -10dB from overflow, signal does not saturate. EXPECT_NEAR(gaindB, measuredGaindB, 0.1); } else { // effective gain saturates. EXPECT_LT(measuredGaindB, gaindB); // we're less than the desired gain. EXPECT_GT(measuredGaindB, lastMeasuredGaindB); // we're more than the previous gain. } lastMeasuredGaindB = measuredGaindB; } ASSERT_EQ(0, AUDIO_EFFECT_LIBRARY_INFO_SYM.release_effect(handle)); }