Loading media/libaudioprocessing/AudioMixerOps.h +113 −50 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ #ifndef ANDROID_AUDIO_MIXER_OPS_H #define ANDROID_AUDIO_MIXER_OPS_H #include <audio_utils/channels.h> #include <audio_utils/primitives.h> #include <system/audio.h> namespace android { Loading Loading @@ -229,15 +231,26 @@ enum { * complexity of working on interleaved streams is now getting * too high, and likely limits compiler optimization. */ template <int MIXTYPE, int NCHAN, // compile-time function. constexpr inline bool usesCenterChannel(audio_channel_mask_t mask) { using namespace audio_utils::channels; for (size_t i = 0; i < std::size(kSideFromChannelIdx); ++i) { if ((mask & (1 << i)) != 0 && kSideFromChannelIdx[i] == AUDIO_GEOMETRY_SIDE_CENTER) { return true; } } return false; } /* * Applies stereo volume to the audio data based on proper left right channel affinity * (templated channel MASK parameter). */ template <int MIXTYPE, audio_channel_mask_t MASK, typename TO, typename TI, typename TV, typename F> void stereoVolumeHelper(TO*& out, const TI*& in, const TV *vol, F f) { static_assert(NCHAN > 0 && NCHAN <= FCC_LIMIT); static_assert(MIXTYPE == MIXTYPE_MULTI_STEREOVOL || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL || MIXTYPE == MIXTYPE_STEREOEXPAND || MIXTYPE == MIXTYPE_MONOEXPAND); void stereoVolumeHelperWithChannelMask(TO*& out, const TI*& in, const TV *vol, F f) { auto proc = [](auto& a, const auto& b) { if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL || MIXTYPE == MIXTYPE_STEREOEXPAND Loading @@ -250,59 +263,109 @@ void stereoVolumeHelper(TO*& out, const TI*& in, const TV *vol, F f) { auto inp = [&in]() -> const TI& { if constexpr (MIXTYPE == MIXTYPE_STEREOEXPAND || MIXTYPE == MIXTYPE_MONOEXPAND) { return *in; return *in; // note STEREOEXPAND assumes replicated L/R channels (see doc below). } else { return *in++; } }; // HALs should only expose the canonical channel masks. proc(*out++, f(inp(), vol[0])); // front left if constexpr (NCHAN == 1) return; proc(*out++, f(inp(), vol[1])); // front right if constexpr (NCHAN == 2) return; if constexpr (NCHAN == 4) { proc(*out++, f(inp(), vol[0])); // back left proc(*out++, f(inp(), vol[1])); // back right return; } // TODO: Precompute center volume if not ramping. std::decay_t<TV> center; constexpr bool USES_CENTER_CHANNEL = usesCenterChannel(MASK); if constexpr (USES_CENTER_CHANNEL) { if constexpr (std::is_floating_point_v<TV>) { center = (vol[0] + vol[1]) * 0.5; // do not use divide } else { center = (vol[0] >> 1) + (vol[1] >> 1); // rounds to 0. } proc(*out++, f(inp(), center)); // center (or 2.1 LFE) if constexpr (NCHAN == 3) return; if constexpr (NCHAN == 5) { proc(*out++, f(inp(), vol[0])); // back left proc(*out++, f(inp(), vol[1])); // back right return; } proc(*out++, f(inp(), center)); // lfe proc(*out++, f(inp(), vol[0])); // back left proc(*out++, f(inp(), vol[1])); // back right if constexpr (NCHAN == 6) return; if constexpr (NCHAN == 7) { proc(*out++, f(inp(), center)); // back center return; } // NCHAN == 8 proc(*out++, f(inp(), vol[0])); // side left proc(*out++, f(inp(), vol[1])); // side right if constexpr (NCHAN > FCC_8) { // Mutes to zero extended surround channels. // 7.1.4 has the correct behavior. // 22.2 has the behavior that FLC and FRC will be mixed instead // of SL and SR and LFE will be center, not left. for (int i = 8; i < NCHAN; ++i) { // TODO: Consider using android::audio_utils::channels::kSideFromChannelIdx proc(*out++, f(inp(), 0.f)); } using namespace audio_utils::channels; // if LFE and LFE2 are both present, they take left and right volume respectively. constexpr unsigned LFE_LFE2 = \ AUDIO_CHANNEL_OUT_LOW_FREQUENCY | AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2; constexpr bool has_LFE_LFE2 = (MASK & LFE_LFE2) == LFE_LFE2; #pragma push_macro("DO_CHANNEL_POSITION") #undef DO_CHANNEL_POSITION #define DO_CHANNEL_POSITION(BIT_INDEX) \ if constexpr ((MASK & (1 << BIT_INDEX)) != 0) { \ constexpr auto side = kSideFromChannelIdx[BIT_INDEX]; \ if constexpr (side == AUDIO_GEOMETRY_SIDE_LEFT || \ has_LFE_LFE2 && (1 << BIT_INDEX) == AUDIO_CHANNEL_OUT_LOW_FREQUENCY) { \ proc(*out++, f(inp(), vol[0])); \ } else if constexpr (side == AUDIO_GEOMETRY_SIDE_RIGHT || \ has_LFE_LFE2 && (1 << BIT_INDEX) == AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2) { \ proc(*out++, f(inp(), vol[1])); \ } else /* constexpr */ { \ proc(*out++, f(inp(), center)); \ } \ } DO_CHANNEL_POSITION(0); DO_CHANNEL_POSITION(1); DO_CHANNEL_POSITION(2); DO_CHANNEL_POSITION(3); DO_CHANNEL_POSITION(4); DO_CHANNEL_POSITION(5); DO_CHANNEL_POSITION(6); DO_CHANNEL_POSITION(7); DO_CHANNEL_POSITION(8); DO_CHANNEL_POSITION(9); DO_CHANNEL_POSITION(10); DO_CHANNEL_POSITION(11); DO_CHANNEL_POSITION(12); DO_CHANNEL_POSITION(13); DO_CHANNEL_POSITION(14); DO_CHANNEL_POSITION(15); DO_CHANNEL_POSITION(16); DO_CHANNEL_POSITION(17); DO_CHANNEL_POSITION(18); DO_CHANNEL_POSITION(19); DO_CHANNEL_POSITION(20); DO_CHANNEL_POSITION(21); DO_CHANNEL_POSITION(22); DO_CHANNEL_POSITION(23); static_assert(FCC_LIMIT <= FCC_24); // Note: this may need to change. #pragma pop_macro("DO_CHANNEL_POSITION") } // These are the channel position masks we expect from the HAL. // See audio_channel_out_mask_from_count() but this is constexpr constexpr inline audio_channel_mask_t canonicalChannelMaskFromCount(size_t channelCount) { constexpr audio_channel_mask_t canonical[] = { [0] = AUDIO_CHANNEL_NONE, [1] = AUDIO_CHANNEL_OUT_MONO, [2] = AUDIO_CHANNEL_OUT_STEREO, [3] = AUDIO_CHANNEL_OUT_2POINT1, [4] = AUDIO_CHANNEL_OUT_QUAD, [5] = AUDIO_CHANNEL_OUT_PENTA, [6] = AUDIO_CHANNEL_OUT_5POINT1, [7] = AUDIO_CHANNEL_OUT_6POINT1, [8] = AUDIO_CHANNEL_OUT_7POINT1, [12] = AUDIO_CHANNEL_OUT_7POINT1POINT4, [24] = AUDIO_CHANNEL_OUT_22POINT2, }; return channelCount < std::size(canonical) ? canonical[channelCount] : AUDIO_CHANNEL_NONE; } template <int MIXTYPE, int NCHAN, typename TO, typename TI, typename TV, typename F> void stereoVolumeHelper(TO*& out, const TI*& in, const TV *vol, F f) { static_assert(NCHAN > 0 && NCHAN <= FCC_LIMIT); static_assert(MIXTYPE == MIXTYPE_MULTI_STEREOVOL || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL || MIXTYPE == MIXTYPE_STEREOEXPAND || MIXTYPE == MIXTYPE_MONOEXPAND); constexpr audio_channel_mask_t MASK{canonicalChannelMaskFromCount(NCHAN)}; if constexpr (MASK == AUDIO_CHANNEL_NONE) { ALOGE("%s: Invalid position count %d", __func__, NCHAN); return; // not a valid system mask, ignore. } stereoVolumeHelperWithChannelMask<MIXTYPE, MASK, TO, TI, TV, F>(out, in, vol, f); } /* Loading media/libaudioprocessing/tests/Android.bp +11 −0 Original line number Diff line number Diff line Loading @@ -76,6 +76,7 @@ cc_binary { // cc_binary { name: "mixerops_objdump", header_libs: ["libaudioutils_headers"], srcs: ["mixerops_objdump.cpp"], } Loading @@ -84,6 +85,16 @@ cc_binary { // cc_benchmark { name: "mixerops_benchmark", header_libs: ["libaudioutils_headers"], srcs: ["mixerops_benchmark.cpp"], static_libs: ["libgoogle-benchmark"], } // // mixerops unit test // cc_test { name: "mixerops_tests", defaults: ["libaudioprocessing_test_defaults"], srcs: ["mixerops_tests.cpp"], } media/libaudioprocessing/tests/mixerops_benchmark.cpp +0 −2 Original line number Diff line number Diff line Loading @@ -16,11 +16,9 @@ #include <inttypes.h> #include <type_traits> #include "../../../../system/media/audio_utils/include/audio_utils/primitives.h" #define LOG_ALWAYS_FATAL(...) #include <../AudioMixerOps.h> #include <benchmark/benchmark.h> using namespace android; Loading media/libaudioprocessing/tests/mixerops_tests.cpp 0 → 100644 +175 −0 Original line number Diff line number Diff line /* * 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. */ //#define LOG_NDEBUG 0 #define LOG_TAG "mixerop_tests" #include <log/log.h> #include <inttypes.h> #include <type_traits> #include <../AudioMixerOps.h> #include <gtest/gtest.h> using namespace android; // Note: gtest templated tests require typenames, not integers. template <int MIXTYPE, int NCHAN> class MixerOpsBasicTest { public: static void testStereoVolume() { using namespace android::audio_utils::channels; constexpr size_t FRAME_COUNT = 1000; constexpr size_t SAMPLE_COUNT = FRAME_COUNT * NCHAN; const float in[SAMPLE_COUNT] = {[0 ... (SAMPLE_COUNT - 1)] = 1.f}; AUDIO_GEOMETRY_SIDE sides[NCHAN]; size_t i = 0; unsigned channel = canonicalChannelMaskFromCount(NCHAN); constexpr unsigned LFE_LFE2 = AUDIO_CHANNEL_OUT_LOW_FREQUENCY | AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2; bool has_LFE_LFE2 = (channel & LFE_LFE2) == LFE_LFE2; while (channel != 0) { const int index = __builtin_ctz(channel); if (has_LFE_LFE2 && (1 << index) == AUDIO_CHANNEL_OUT_LOW_FREQUENCY) { sides[i++] = AUDIO_GEOMETRY_SIDE_LEFT; // special case } else if (has_LFE_LFE2 && (1 << index) == AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2) { sides[i++] = AUDIO_GEOMETRY_SIDE_RIGHT; // special case } else { sides[i++] = sideFromChannelIdx(index); } channel &= ~(1 << index); } float vola[2] = {1.f, 0.f}; // left volume at max. float out[SAMPLE_COUNT]{}; float aux[FRAME_COUNT]{}; float volaux = 0.5; { volumeMulti<MIXTYPE, NCHAN>(out, FRAME_COUNT, in, aux, vola, volaux); const float *outp = out; const float *auxp = aux; const float left = vola[0]; const float center = (vola[0] + vola[1]) * 0.5; const float right = vola[1]; for (size_t i = 0; i < FRAME_COUNT; ++i) { for (size_t j = 0; j < NCHAN; ++j) { const float audio = *outp++; if (sides[j] == AUDIO_GEOMETRY_SIDE_LEFT) { EXPECT_EQ(left, audio); } else if (sides[j] == AUDIO_GEOMETRY_SIDE_CENTER) { EXPECT_EQ(center, audio); } else { EXPECT_EQ(right, audio); } } EXPECT_EQ(volaux, *auxp++); // works if all channels contain 1.f } } float volb[2] = {0.f, 0.5f}; // right volume at half max. { // this accumulates into out, aux. // float out[SAMPLE_COUNT]{}; // float aux[FRAME_COUNT]{}; volumeMulti<MIXTYPE, NCHAN>(out, FRAME_COUNT, in, aux, volb, volaux); const float *outp = out; const float *auxp = aux; const float left = vola[0] + volb[0]; const float center = (vola[0] + vola[1] + volb[0] + volb[1]) * 0.5; const float right = vola[1] + volb[1]; for (size_t i = 0; i < FRAME_COUNT; ++i) { for (size_t j = 0; j < NCHAN; ++j) { const float audio = *outp++; if (sides[j] == AUDIO_GEOMETRY_SIDE_LEFT) { EXPECT_EQ(left, audio); } else if (sides[j] == AUDIO_GEOMETRY_SIDE_CENTER) { EXPECT_EQ(center, audio); } else { EXPECT_EQ(right, audio); } } // aux is accumulated so 2x the amplitude EXPECT_EQ(volaux * 2.f, *auxp++); // works if all channels contain 1.f } } { // test aux as derived from out. // AUX channel is the weighted sum of all of the output channels prior to volume // adjustment. We must set L and R to the same volume to allow computation // of AUX from the output values. const float volmono = 0.25f; const float vollr[2] = {volmono, volmono}; // all the same. float out[SAMPLE_COUNT]{}; float aux[FRAME_COUNT]{}; volumeMulti<MIXTYPE, NCHAN>(out, FRAME_COUNT, in, aux, vollr, volaux); const float *outp = out; const float *auxp = aux; for (size_t i = 0; i < FRAME_COUNT; ++i) { float accum = 0.f; for (size_t j = 0; j < NCHAN; ++j) { accum += *outp++; } EXPECT_EQ(accum / NCHAN * volaux / volmono, *auxp++); } } } }; TEST(mixerops, stereovolume_1) { // Note: mono not used for output sinks yet. MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 1>::testStereoVolume(); } TEST(mixerops, stereovolume_2) { MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 2>::testStereoVolume(); } TEST(mixerops, stereovolume_3) { MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 3>::testStereoVolume(); } TEST(mixerops, stereovolume_4) { MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 4>::testStereoVolume(); } TEST(mixerops, stereovolume_5) { MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 5>::testStereoVolume(); } TEST(mixerops, stereovolume_6) { MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 6>::testStereoVolume(); } TEST(mixerops, stereovolume_7) { MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 7>::testStereoVolume(); } TEST(mixerops, stereovolume_8) { MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 8>::testStereoVolume(); } TEST(mixerops, stereovolume_12) { if constexpr (FCC_LIMIT >= 12) { // NOTE: FCC_LIMIT is an enum, so can't #if MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 12>::testStereoVolume(); } } TEST(mixerops, stereovolume_24) { if constexpr (FCC_LIMIT >= 24) { MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 24>::testStereoVolume(); } } TEST(mixerops, channel_equivalence) { // we must match the constexpr function with the system determined channel mask from count. for (size_t i = 0; i < FCC_LIMIT; ++i) { const audio_channel_mask_t actual = canonicalChannelMaskFromCount(i); const audio_channel_mask_t system = audio_channel_out_mask_from_count(i); if (system == AUDIO_CHANNEL_INVALID) continue; EXPECT_EQ(system, actual); } } Loading
media/libaudioprocessing/AudioMixerOps.h +113 −50 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ #ifndef ANDROID_AUDIO_MIXER_OPS_H #define ANDROID_AUDIO_MIXER_OPS_H #include <audio_utils/channels.h> #include <audio_utils/primitives.h> #include <system/audio.h> namespace android { Loading Loading @@ -229,15 +231,26 @@ enum { * complexity of working on interleaved streams is now getting * too high, and likely limits compiler optimization. */ template <int MIXTYPE, int NCHAN, // compile-time function. constexpr inline bool usesCenterChannel(audio_channel_mask_t mask) { using namespace audio_utils::channels; for (size_t i = 0; i < std::size(kSideFromChannelIdx); ++i) { if ((mask & (1 << i)) != 0 && kSideFromChannelIdx[i] == AUDIO_GEOMETRY_SIDE_CENTER) { return true; } } return false; } /* * Applies stereo volume to the audio data based on proper left right channel affinity * (templated channel MASK parameter). */ template <int MIXTYPE, audio_channel_mask_t MASK, typename TO, typename TI, typename TV, typename F> void stereoVolumeHelper(TO*& out, const TI*& in, const TV *vol, F f) { static_assert(NCHAN > 0 && NCHAN <= FCC_LIMIT); static_assert(MIXTYPE == MIXTYPE_MULTI_STEREOVOL || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL || MIXTYPE == MIXTYPE_STEREOEXPAND || MIXTYPE == MIXTYPE_MONOEXPAND); void stereoVolumeHelperWithChannelMask(TO*& out, const TI*& in, const TV *vol, F f) { auto proc = [](auto& a, const auto& b) { if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL || MIXTYPE == MIXTYPE_STEREOEXPAND Loading @@ -250,59 +263,109 @@ void stereoVolumeHelper(TO*& out, const TI*& in, const TV *vol, F f) { auto inp = [&in]() -> const TI& { if constexpr (MIXTYPE == MIXTYPE_STEREOEXPAND || MIXTYPE == MIXTYPE_MONOEXPAND) { return *in; return *in; // note STEREOEXPAND assumes replicated L/R channels (see doc below). } else { return *in++; } }; // HALs should only expose the canonical channel masks. proc(*out++, f(inp(), vol[0])); // front left if constexpr (NCHAN == 1) return; proc(*out++, f(inp(), vol[1])); // front right if constexpr (NCHAN == 2) return; if constexpr (NCHAN == 4) { proc(*out++, f(inp(), vol[0])); // back left proc(*out++, f(inp(), vol[1])); // back right return; } // TODO: Precompute center volume if not ramping. std::decay_t<TV> center; constexpr bool USES_CENTER_CHANNEL = usesCenterChannel(MASK); if constexpr (USES_CENTER_CHANNEL) { if constexpr (std::is_floating_point_v<TV>) { center = (vol[0] + vol[1]) * 0.5; // do not use divide } else { center = (vol[0] >> 1) + (vol[1] >> 1); // rounds to 0. } proc(*out++, f(inp(), center)); // center (or 2.1 LFE) if constexpr (NCHAN == 3) return; if constexpr (NCHAN == 5) { proc(*out++, f(inp(), vol[0])); // back left proc(*out++, f(inp(), vol[1])); // back right return; } proc(*out++, f(inp(), center)); // lfe proc(*out++, f(inp(), vol[0])); // back left proc(*out++, f(inp(), vol[1])); // back right if constexpr (NCHAN == 6) return; if constexpr (NCHAN == 7) { proc(*out++, f(inp(), center)); // back center return; } // NCHAN == 8 proc(*out++, f(inp(), vol[0])); // side left proc(*out++, f(inp(), vol[1])); // side right if constexpr (NCHAN > FCC_8) { // Mutes to zero extended surround channels. // 7.1.4 has the correct behavior. // 22.2 has the behavior that FLC and FRC will be mixed instead // of SL and SR and LFE will be center, not left. for (int i = 8; i < NCHAN; ++i) { // TODO: Consider using android::audio_utils::channels::kSideFromChannelIdx proc(*out++, f(inp(), 0.f)); } using namespace audio_utils::channels; // if LFE and LFE2 are both present, they take left and right volume respectively. constexpr unsigned LFE_LFE2 = \ AUDIO_CHANNEL_OUT_LOW_FREQUENCY | AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2; constexpr bool has_LFE_LFE2 = (MASK & LFE_LFE2) == LFE_LFE2; #pragma push_macro("DO_CHANNEL_POSITION") #undef DO_CHANNEL_POSITION #define DO_CHANNEL_POSITION(BIT_INDEX) \ if constexpr ((MASK & (1 << BIT_INDEX)) != 0) { \ constexpr auto side = kSideFromChannelIdx[BIT_INDEX]; \ if constexpr (side == AUDIO_GEOMETRY_SIDE_LEFT || \ has_LFE_LFE2 && (1 << BIT_INDEX) == AUDIO_CHANNEL_OUT_LOW_FREQUENCY) { \ proc(*out++, f(inp(), vol[0])); \ } else if constexpr (side == AUDIO_GEOMETRY_SIDE_RIGHT || \ has_LFE_LFE2 && (1 << BIT_INDEX) == AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2) { \ proc(*out++, f(inp(), vol[1])); \ } else /* constexpr */ { \ proc(*out++, f(inp(), center)); \ } \ } DO_CHANNEL_POSITION(0); DO_CHANNEL_POSITION(1); DO_CHANNEL_POSITION(2); DO_CHANNEL_POSITION(3); DO_CHANNEL_POSITION(4); DO_CHANNEL_POSITION(5); DO_CHANNEL_POSITION(6); DO_CHANNEL_POSITION(7); DO_CHANNEL_POSITION(8); DO_CHANNEL_POSITION(9); DO_CHANNEL_POSITION(10); DO_CHANNEL_POSITION(11); DO_CHANNEL_POSITION(12); DO_CHANNEL_POSITION(13); DO_CHANNEL_POSITION(14); DO_CHANNEL_POSITION(15); DO_CHANNEL_POSITION(16); DO_CHANNEL_POSITION(17); DO_CHANNEL_POSITION(18); DO_CHANNEL_POSITION(19); DO_CHANNEL_POSITION(20); DO_CHANNEL_POSITION(21); DO_CHANNEL_POSITION(22); DO_CHANNEL_POSITION(23); static_assert(FCC_LIMIT <= FCC_24); // Note: this may need to change. #pragma pop_macro("DO_CHANNEL_POSITION") } // These are the channel position masks we expect from the HAL. // See audio_channel_out_mask_from_count() but this is constexpr constexpr inline audio_channel_mask_t canonicalChannelMaskFromCount(size_t channelCount) { constexpr audio_channel_mask_t canonical[] = { [0] = AUDIO_CHANNEL_NONE, [1] = AUDIO_CHANNEL_OUT_MONO, [2] = AUDIO_CHANNEL_OUT_STEREO, [3] = AUDIO_CHANNEL_OUT_2POINT1, [4] = AUDIO_CHANNEL_OUT_QUAD, [5] = AUDIO_CHANNEL_OUT_PENTA, [6] = AUDIO_CHANNEL_OUT_5POINT1, [7] = AUDIO_CHANNEL_OUT_6POINT1, [8] = AUDIO_CHANNEL_OUT_7POINT1, [12] = AUDIO_CHANNEL_OUT_7POINT1POINT4, [24] = AUDIO_CHANNEL_OUT_22POINT2, }; return channelCount < std::size(canonical) ? canonical[channelCount] : AUDIO_CHANNEL_NONE; } template <int MIXTYPE, int NCHAN, typename TO, typename TI, typename TV, typename F> void stereoVolumeHelper(TO*& out, const TI*& in, const TV *vol, F f) { static_assert(NCHAN > 0 && NCHAN <= FCC_LIMIT); static_assert(MIXTYPE == MIXTYPE_MULTI_STEREOVOL || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL || MIXTYPE == MIXTYPE_STEREOEXPAND || MIXTYPE == MIXTYPE_MONOEXPAND); constexpr audio_channel_mask_t MASK{canonicalChannelMaskFromCount(NCHAN)}; if constexpr (MASK == AUDIO_CHANNEL_NONE) { ALOGE("%s: Invalid position count %d", __func__, NCHAN); return; // not a valid system mask, ignore. } stereoVolumeHelperWithChannelMask<MIXTYPE, MASK, TO, TI, TV, F>(out, in, vol, f); } /* Loading
media/libaudioprocessing/tests/Android.bp +11 −0 Original line number Diff line number Diff line Loading @@ -76,6 +76,7 @@ cc_binary { // cc_binary { name: "mixerops_objdump", header_libs: ["libaudioutils_headers"], srcs: ["mixerops_objdump.cpp"], } Loading @@ -84,6 +85,16 @@ cc_binary { // cc_benchmark { name: "mixerops_benchmark", header_libs: ["libaudioutils_headers"], srcs: ["mixerops_benchmark.cpp"], static_libs: ["libgoogle-benchmark"], } // // mixerops unit test // cc_test { name: "mixerops_tests", defaults: ["libaudioprocessing_test_defaults"], srcs: ["mixerops_tests.cpp"], }
media/libaudioprocessing/tests/mixerops_benchmark.cpp +0 −2 Original line number Diff line number Diff line Loading @@ -16,11 +16,9 @@ #include <inttypes.h> #include <type_traits> #include "../../../../system/media/audio_utils/include/audio_utils/primitives.h" #define LOG_ALWAYS_FATAL(...) #include <../AudioMixerOps.h> #include <benchmark/benchmark.h> using namespace android; Loading
media/libaudioprocessing/tests/mixerops_tests.cpp 0 → 100644 +175 −0 Original line number Diff line number Diff line /* * 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. */ //#define LOG_NDEBUG 0 #define LOG_TAG "mixerop_tests" #include <log/log.h> #include <inttypes.h> #include <type_traits> #include <../AudioMixerOps.h> #include <gtest/gtest.h> using namespace android; // Note: gtest templated tests require typenames, not integers. template <int MIXTYPE, int NCHAN> class MixerOpsBasicTest { public: static void testStereoVolume() { using namespace android::audio_utils::channels; constexpr size_t FRAME_COUNT = 1000; constexpr size_t SAMPLE_COUNT = FRAME_COUNT * NCHAN; const float in[SAMPLE_COUNT] = {[0 ... (SAMPLE_COUNT - 1)] = 1.f}; AUDIO_GEOMETRY_SIDE sides[NCHAN]; size_t i = 0; unsigned channel = canonicalChannelMaskFromCount(NCHAN); constexpr unsigned LFE_LFE2 = AUDIO_CHANNEL_OUT_LOW_FREQUENCY | AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2; bool has_LFE_LFE2 = (channel & LFE_LFE2) == LFE_LFE2; while (channel != 0) { const int index = __builtin_ctz(channel); if (has_LFE_LFE2 && (1 << index) == AUDIO_CHANNEL_OUT_LOW_FREQUENCY) { sides[i++] = AUDIO_GEOMETRY_SIDE_LEFT; // special case } else if (has_LFE_LFE2 && (1 << index) == AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2) { sides[i++] = AUDIO_GEOMETRY_SIDE_RIGHT; // special case } else { sides[i++] = sideFromChannelIdx(index); } channel &= ~(1 << index); } float vola[2] = {1.f, 0.f}; // left volume at max. float out[SAMPLE_COUNT]{}; float aux[FRAME_COUNT]{}; float volaux = 0.5; { volumeMulti<MIXTYPE, NCHAN>(out, FRAME_COUNT, in, aux, vola, volaux); const float *outp = out; const float *auxp = aux; const float left = vola[0]; const float center = (vola[0] + vola[1]) * 0.5; const float right = vola[1]; for (size_t i = 0; i < FRAME_COUNT; ++i) { for (size_t j = 0; j < NCHAN; ++j) { const float audio = *outp++; if (sides[j] == AUDIO_GEOMETRY_SIDE_LEFT) { EXPECT_EQ(left, audio); } else if (sides[j] == AUDIO_GEOMETRY_SIDE_CENTER) { EXPECT_EQ(center, audio); } else { EXPECT_EQ(right, audio); } } EXPECT_EQ(volaux, *auxp++); // works if all channels contain 1.f } } float volb[2] = {0.f, 0.5f}; // right volume at half max. { // this accumulates into out, aux. // float out[SAMPLE_COUNT]{}; // float aux[FRAME_COUNT]{}; volumeMulti<MIXTYPE, NCHAN>(out, FRAME_COUNT, in, aux, volb, volaux); const float *outp = out; const float *auxp = aux; const float left = vola[0] + volb[0]; const float center = (vola[0] + vola[1] + volb[0] + volb[1]) * 0.5; const float right = vola[1] + volb[1]; for (size_t i = 0; i < FRAME_COUNT; ++i) { for (size_t j = 0; j < NCHAN; ++j) { const float audio = *outp++; if (sides[j] == AUDIO_GEOMETRY_SIDE_LEFT) { EXPECT_EQ(left, audio); } else if (sides[j] == AUDIO_GEOMETRY_SIDE_CENTER) { EXPECT_EQ(center, audio); } else { EXPECT_EQ(right, audio); } } // aux is accumulated so 2x the amplitude EXPECT_EQ(volaux * 2.f, *auxp++); // works if all channels contain 1.f } } { // test aux as derived from out. // AUX channel is the weighted sum of all of the output channels prior to volume // adjustment. We must set L and R to the same volume to allow computation // of AUX from the output values. const float volmono = 0.25f; const float vollr[2] = {volmono, volmono}; // all the same. float out[SAMPLE_COUNT]{}; float aux[FRAME_COUNT]{}; volumeMulti<MIXTYPE, NCHAN>(out, FRAME_COUNT, in, aux, vollr, volaux); const float *outp = out; const float *auxp = aux; for (size_t i = 0; i < FRAME_COUNT; ++i) { float accum = 0.f; for (size_t j = 0; j < NCHAN; ++j) { accum += *outp++; } EXPECT_EQ(accum / NCHAN * volaux / volmono, *auxp++); } } } }; TEST(mixerops, stereovolume_1) { // Note: mono not used for output sinks yet. MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 1>::testStereoVolume(); } TEST(mixerops, stereovolume_2) { MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 2>::testStereoVolume(); } TEST(mixerops, stereovolume_3) { MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 3>::testStereoVolume(); } TEST(mixerops, stereovolume_4) { MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 4>::testStereoVolume(); } TEST(mixerops, stereovolume_5) { MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 5>::testStereoVolume(); } TEST(mixerops, stereovolume_6) { MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 6>::testStereoVolume(); } TEST(mixerops, stereovolume_7) { MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 7>::testStereoVolume(); } TEST(mixerops, stereovolume_8) { MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 8>::testStereoVolume(); } TEST(mixerops, stereovolume_12) { if constexpr (FCC_LIMIT >= 12) { // NOTE: FCC_LIMIT is an enum, so can't #if MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 12>::testStereoVolume(); } } TEST(mixerops, stereovolume_24) { if constexpr (FCC_LIMIT >= 24) { MixerOpsBasicTest<MIXTYPE_MULTI_STEREOVOL, 24>::testStereoVolume(); } } TEST(mixerops, channel_equivalence) { // we must match the constexpr function with the system determined channel mask from count. for (size_t i = 0; i < FCC_LIMIT; ++i) { const audio_channel_mask_t actual = canonicalChannelMaskFromCount(i); const audio_channel_mask_t system = audio_channel_out_mask_from_count(i); if (system == AUDIO_CHANNEL_INVALID) continue; EXPECT_EQ(system, actual); } }