1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <audio_utils/ChannelMix.h>
18
19 namespace android::audio_utils::channels {
20
21 /**
22 * The ChannelMix code relies on sparse matrix optimization for speed.
23 *
24 * This requires -ffast-math to be specified.
25 */
26
27
28 /*
29 Implementation detail:
30
31 We "compute" the channel mix matrix by constexpr computation,
32 but alternatively we could use a straight 2D array initialization.
33
34 The thought is that the channel mix matrix by computation is easier
35 to keep consistent with modifications.
36 */
37
38 namespace {
39 // A container for the channel matrix
40 template <audio_channel_mask_t INPUT_CHANNEL_MASK, audio_channel_mask_t OUTPUT_CHANNEL_MASK>
41 struct ChannelMatrixContainer {
42 static inline constexpr size_t INPUT_CHANNEL_COUNT =
43 audio_channel_count_from_out_mask(INPUT_CHANNEL_MASK);
44 static inline constexpr size_t OUTPUT_CHANNEL_COUNT =
45 audio_channel_count_from_out_mask(OUTPUT_CHANNEL_MASK);
46 float f[INPUT_CHANNEL_COUNT][OUTPUT_CHANNEL_COUNT];
47 };
48
49 template <audio_channel_mask_t INPUT_CHANNEL_MASK, audio_channel_mask_t OUTPUT_CHANNEL_MASK>
computeMatrix()50 constexpr ChannelMatrixContainer<INPUT_CHANNEL_MASK, OUTPUT_CHANNEL_MASK> computeMatrix() {
51 ChannelMatrixContainer<INPUT_CHANNEL_MASK, OUTPUT_CHANNEL_MASK> channelMatrix{};
52 // Compiler bug: cannot check result of this through static_assert.
53 (void)fillChannelMatrix<OUTPUT_CHANNEL_MASK>(INPUT_CHANNEL_MASK, channelMatrix.f);
54 return channelMatrix;
55 }
56
57 } // namespace
58
59 /**
60 * Remixes a multichannel signal of specified number of channels
61 *
62 * INPUT_CHANNEL_MASK the src input.
63 * OUTPUT_CHANNEL_MASK the dst output.
64 * ACCUMULATE is true if the remix is added to the destination or
65 * false if the remix replaces the destination.
66 *
67 * \param src multichannel audio buffer to remix
68 * \param dst remixed stereo audio samples
69 * \param frameCount number of multichannel frames to remix
70 *
71 * \return false if the CHANNEL_COUNT is not supported.
72 */
73 template <audio_channel_mask_t INPUT_CHANNEL_MASK,
74 audio_channel_mask_t OUTPUT_CHANNEL_MASK, bool ACCUMULATE>
sparseChannelMatrixMultiply(const float * src,float * dst,size_t frameCount)75 bool sparseChannelMatrixMultiply(const float *src, float *dst, size_t frameCount) {
76 static constexpr auto s = computeMatrix<INPUT_CHANNEL_MASK, OUTPUT_CHANNEL_MASK>();
77
78 // matrix multiply
79 if (INPUT_CHANNEL_MASK == AUDIO_CHANNEL_NONE) return false;
80 for (;frameCount > 0; --frameCount) {
81 float ch[s.OUTPUT_CHANNEL_COUNT]{};
82 #pragma unroll
83 for (size_t i = 0; i < s.INPUT_CHANNEL_COUNT; ++i) {
84 const float (&array)[s.OUTPUT_CHANNEL_COUNT] = s.f[i];
85 #pragma unroll
86 for (size_t j = 0; j < s.OUTPUT_CHANNEL_COUNT; ++j) {
87 ch[j] += array[j] * src[i];
88 }
89 }
90 if constexpr (ACCUMULATE) {
91 #pragma unroll
92 for (size_t j = 0; j < s.OUTPUT_CHANNEL_COUNT; ++j) {
93 ch[j] += dst[j];
94 }
95 }
96 #pragma unroll
97 for (size_t j = 0; j < s.OUTPUT_CHANNEL_COUNT; ++j) {
98 dst[j] = clamp(ch[j]);
99 }
100 src += s.INPUT_CHANNEL_COUNT;
101 dst += s.OUTPUT_CHANNEL_COUNT;
102 }
103 return true;
104 }
105
106 // Create accelerated instances
107
108 #define INSTANTIATE(INPUT_MASK, OUTPUT_MASK) \
109 template bool \
110 sparseChannelMatrixMultiply<INPUT_MASK, OUTPUT_MASK, true>( \
111 const float *src, float *dst, size_t frameCount); \
112 template bool \
113 sparseChannelMatrixMultiply<INPUT_MASK, OUTPUT_MASK, false>( \
114 const float *src, float *dst, size_t frameCount); \
115
116 #define INSTANTIATE_MASKS(CHANNEL) \
117 INSTANTIATE(AUDIO_CHANNEL_OUT_STEREO, (CHANNEL)) \
118 INSTANTIATE(AUDIO_CHANNEL_OUT_QUAD_BACK, (CHANNEL)) \
119 INSTANTIATE(AUDIO_CHANNEL_OUT_5POINT1_BACK, (CHANNEL)) \
120 INSTANTIATE(AUDIO_CHANNEL_OUT_7POINT1, (CHANNEL)) \
121 INSTANTIATE(AUDIO_CHANNEL_OUT_5POINT1POINT2, (CHANNEL)) \
122 INSTANTIATE(AUDIO_CHANNEL_OUT_5POINT1POINT4, (CHANNEL)) \
123 INSTANTIATE(AUDIO_CHANNEL_OUT_7POINT1POINT2, (CHANNEL)) \
124 INSTANTIATE(AUDIO_CHANNEL_OUT_7POINT1POINT4, (CHANNEL)) \
125 INSTANTIATE(AUDIO_CHANNEL_OUT_22POINT2, (CHANNEL))
126
127 INSTANTIATE_MASKS(AUDIO_CHANNEL_OUT_STEREO)
INSTANTIATE_MASKS(AUDIO_CHANNEL_OUT_5POINT1)128 INSTANTIATE_MASKS(AUDIO_CHANNEL_OUT_5POINT1)
129 INSTANTIATE_MASKS(AUDIO_CHANNEL_OUT_7POINT1)
130 INSTANTIATE_MASKS(AUDIO_CHANNEL_OUT_7POINT1POINT4)
131
132 /* static */
133 std::shared_ptr<IChannelMix> IChannelMix::create(audio_channel_mask_t outputChannelMask) {
134 switch (outputChannelMask) {
135 case AUDIO_CHANNEL_OUT_STEREO:
136 return std::make_shared<ChannelMix<AUDIO_CHANNEL_OUT_STEREO>>();
137 case AUDIO_CHANNEL_OUT_5POINT1:
138 return std::make_shared<ChannelMix<AUDIO_CHANNEL_OUT_5POINT1>>();
139 case AUDIO_CHANNEL_OUT_7POINT1:
140 return std::make_shared<ChannelMix<AUDIO_CHANNEL_OUT_7POINT1>>();
141 case AUDIO_CHANNEL_OUT_7POINT1POINT4:
142 return std::make_shared<ChannelMix<AUDIO_CHANNEL_OUT_7POINT1POINT4>>();
143 default:
144 return {};
145 }
146 }
147
148 /* static */
isOutputChannelMaskSupported(audio_channel_mask_t outputChannelMask)149 bool IChannelMix::isOutputChannelMaskSupported(audio_channel_mask_t outputChannelMask) {
150 switch (outputChannelMask) {
151 case AUDIO_CHANNEL_OUT_STEREO:
152 case AUDIO_CHANNEL_OUT_5POINT1:
153 case AUDIO_CHANNEL_OUT_7POINT1:
154 case AUDIO_CHANNEL_OUT_7POINT1POINT4:
155 return true;
156 default:
157 return false;
158 }
159 }
160
161 } // android::audio_utils::channels
162