-rw-r--r-- | audio_utils/Balance.cpp | 19 | ||||
-rw-r--r-- | audio_utils/include/audio_utils/Balance.h | 14 | ||||
-rw-r--r-- | audio_utils/tests/Android.bp | 19 | ||||
-rw-r--r-- | audio_utils/tests/balance_tests.cpp | 124 |
diff --git a/audio_utils/Balance.cpp b/audio_utils/Balance.cpp index dca9e58..df0e76b 100644 --- a/audio_utils/Balance.cpp +++ b/audio_utils/Balance.cpp @@ -77,18 +77,37 @@ void Balance::setChannelMask(audio_channel_mask_t channelMask) 1, // AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT = 0x20000u, 0, // AUDIO_CHANNEL_OUT_TOP_SIDE_LEFT = 0x40000u, 1, // AUDIO_CHANNEL_OUT_TOP_SIDE_RIGHT = 0x80000u, + 0, // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_LEFT = 0x100000u, + 2, // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_CENTER = 0x200000u, + 1, // AUDIO_CHANNEL_OUT_BOTTOM_FRONT_RIGHT = 0x400000u, + 2, // AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2 = 0x800000u, }; mSides.resize(mChannelCount); + // If LFE and LFE2 both exist, it should be L and R in 22.2 + int lfe = -1; + int lfe2 = -1; + constexpr unsigned LFE_CHANNEL_INDEX = 3; + constexpr unsigned LFE2_CHANNEL_INDEX = 23; for (unsigned i = 0, channel = channelMask; channel != 0; ++i) { const int index = __builtin_ctz(channel); if (static_cast<std::size_t>(index) < std::size(sideFromChannel)) { mSides[i] = sideFromChannel[index]; + // Keep track of LFE indices + if (index == LFE_CHANNEL_INDEX) { + lfe = i; + } else if (index == LFE2_CHANNEL_INDEX) { + lfe2 = i; + } } else { mSides[i] = 2; // consider center } channel &= ~(1 << index); } + if (lfe >= 0 && lfe2 >= 0) { // if both LFEs exist assign to L and R. + mSides[lfe] = 0; + mSides[lfe2] = 1; + } setBalance(balance); // recompute balance } diff --git a/audio_utils/include/audio_utils/Balance.h b/audio_utils/include/audio_utils/Balance.h index da9b836..4a07c09 100644 --- a/audio_utils/include/audio_utils/Balance.h +++ b/audio_utils/include/audio_utils/Balance.h @@ -71,6 +71,13 @@ public: } /** + * \brief Returns whether volume change is ramped. + */ + bool getRamp() const { + return mRamp; + } + + /** * \brief Sets the channel mask for data passed in. * * setChannelMask() must called before process() to set @@ -95,6 +102,13 @@ public: void setBalance(float balance); /** + * \brief Returns the current balance. + */ + float getBalance() const { + return mBalance; + } + + /** * \brief Processes balance for audio data. * * setChannelMask() should be called at least once before calling process() diff --git a/audio_utils/tests/Android.bp b/audio_utils/tests/Android.bp index 9326cb5..fb0dd49 100644 --- a/audio_utils/tests/Android.bp +++ b/audio_utils/tests/Android.bp @@ -8,6 +8,25 @@ package { default_applicable_licenses: ["system_media_license"], } +cc_test { + name: "balance_tests", + host_supported: true, + + srcs: [ + "balance_tests.cpp", + ], + + static_libs: [ + "libaudioutils", + ], + + cflags: [ + "-Wall", + "-Werror", + "-Wextra", + ], +} + cc_binary { name: "biquad_filter", host_supported: true, diff --git a/audio_utils/tests/balance_tests.cpp b/audio_utils/tests/balance_tests.cpp new file mode 100644 index 0000000..aa115d7 --- /dev/null +++ b/audio_utils/tests/balance_tests.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2021 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 <audio_utils/Balance.h> +#include <gtest/gtest.h> +#include <vector> + +TEST(audio_utils_balance, stereo) { + // disable ramping so we can check single frame processing. + android::audio_utils::Balance balance(false /* ramp */); + ASSERT_EQ(false, balance.getRamp()); + + balance.setChannelMask(AUDIO_CHANNEL_OUT_STEREO); + std::vector<float> buffer = {1.f, -1.f}; + + // balance of 0 is no change. + ASSERT_EQ(0.f, balance.getBalance()); + balance.process(buffer.data(), 1 /* frames */); + ASSERT_EQ((std::vector<float>{1.f, -1.f}), buffer); + + // balance of 1.f is right. + balance.setBalance(1.f); + ASSERT_EQ(1.f, balance.getBalance()); + balance.process(buffer.data(), 1 /* frames */); + ASSERT_EQ((std::vector<float>{0.f, -1.f}), buffer); + + // balance of -1.f is left. + buffer = {1.f, -1.f}; + balance.setBalance(-1.f); // to left + ASSERT_EQ(-1.f, balance.getBalance()); + balance.process(buffer.data(), 1 /* frames */); + ASSERT_EQ((std::vector<float>{1.f, 0.f}), buffer); +} + +TEST(audio_utils_balance, 7point1) { + // disable ramping so we can check single frame processing. + android::audio_utils::Balance balance(false /* ramp */); + ASSERT_EQ(false, balance.getRamp()); + + balance.setChannelMask(AUDIO_CHANNEL_OUT_7POINT1); + // FL, FR, FC, LFE, BL, BR, SL, SR + std::vector<float> buffer = {1.f, -1.f, 0.5f, 0.25f, 0.75f, 0.75f, 0.125f, 0.125f}; + + // balance of 0 is no change. + ASSERT_EQ(0.f, balance.getBalance()); + balance.process(buffer.data(), 1 /* frames */); + ASSERT_EQ((std::vector<float>{1.f, -1.f, 0.5f, 0.25f, 0.75f, 0.75f, 0.125f, 0.125f}), buffer); + + // balance of 1.f is right. + balance.setBalance(1.f); + balance.process(buffer.data(), 1 /* frames */); + ASSERT_EQ((std::vector<float>{0.f, -1.f, 0.5f, 0.25f, 0.f, 0.75f, 0.f, 0.125f}), buffer); + + // balance of -1.f is left. + buffer = {1.f, -1.f, 0.5f, 0.25f, 0.75f, 0.75f, 0.125f, 0.125f}; + balance.setBalance(-1.f); // to left + ASSERT_EQ(-1.f, balance.getBalance()); + balance.process(buffer.data(), 1 /* frames */); + ASSERT_EQ((std::vector<float>{1.f, 0.f, 0.5f, 0.25f, 0.75f, 0.f, 0.125f, 0.f}), buffer); +} + +TEST(audio_utils_balance, lfe) { + // disable ramping so we can check single frame processing. + android::audio_utils::Balance balance(false /* ramp */); + ASSERT_EQ(false, balance.getRamp()); + std::vector<float> buffer = {1.f, -1.f}; + + // NOTE: single channel falls under mono exception (we ignore balance) + // so we pair with another "center" channel. + // LFE by itself is considered "center". + for (auto channelMask : { + (AUDIO_CHANNEL_OUT_FRONT_CENTER | AUDIO_CHANNEL_OUT_LOW_FREQUENCY), + (AUDIO_CHANNEL_OUT_FRONT_CENTER | AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2), + }) { + balance.setChannelMask((audio_channel_mask_t)channelMask); + + // balance of 0 is no change. + balance.setBalance(0.f); + balance.process(buffer.data(), 1 /* frames */); + ASSERT_EQ((std::vector<float>{1.f, -1.f}), buffer); + + // balance of 1.f is right. (center unaffected) + balance.setBalance(1.f); + balance.process(buffer.data(), 1 /* frames */); + ASSERT_EQ((std::vector<float>{1.f, -1.f}), buffer); + + // balance of -1.f is left. (center unaffected) + balance.setBalance(-1.f); + balance.process(buffer.data(), 1 /* frames */); + ASSERT_EQ((std::vector<float>{1.f, -1.f}), buffer); + } + + // If both LFE and LFE2 are present, we assume L/R. + balance.setChannelMask((audio_channel_mask_t) + (AUDIO_CHANNEL_OUT_LOW_FREQUENCY | AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2)); + // balance of 0 is no change. + balance.setBalance(0.f); // to left + balance.process(buffer.data(), 1 /* frames */); + ASSERT_EQ((std::vector<float>{1.f, -1.f}), buffer); + + // balance of 1.f is right. + balance.setBalance(1.f); + balance.process(buffer.data(), 1 /* frames */); + ASSERT_EQ((std::vector<float>{0.f, -1.f}), buffer); + + // balance of -1.f is left. + buffer = {1.f, -1.f}; + balance.setBalance(-1.f); // to left + balance.process(buffer.data(), 1 /* frames */); + ASSERT_EQ((std::vector<float>{1.f, 0.f}), buffer); +} |